From 09973a6a561b0f51f9f81163837aa36e5ae89eb1 Mon Sep 17 00:00:00 2001 From: Zander Brown Date: Tue, 29 May 2018 17:43:09 +0100 Subject: [PATCH 01/45] Initial playback ... and not a lot more. Hit play and the podcast will play, press play on something else and that will play instead --- Cargo.lock | 142 +++++++++++++++++++++++++++++ hammond-gtk/Cargo.toml | 2 + hammond-gtk/src/app.rs | 15 ++- hammond-gtk/src/main.rs | 4 + hammond-gtk/src/widgets/episode.rs | 19 +++- org.gnome.Hammond.json | 1 + 6 files changed, 178 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c1c50ad..663be4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -701,6 +701,122 @@ dependencies = [ "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "gstreamer" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "muldiv 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num-rational 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gstreamer-base" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-base-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gstreamer-base-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gstreamer-player" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-player-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-video 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gstreamer-player-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-video-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gstreamer-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gstreamer-video" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-base 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-base-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-video-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gstreamer-video-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-base-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "gtk" version = "0.4.1" @@ -805,6 +921,8 @@ dependencies = [ "gdk-pixbuf 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "gio 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "glib 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", + "gstreamer-player 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "gtk 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "hammond-data 0.1.0", "hammond-downloader 0.1.0", @@ -1185,6 +1303,11 @@ dependencies = [ "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "muldiv" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "native-tls" version = "0.1.5" @@ -1230,6 +1353,15 @@ dependencies = [ "num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "num-rational" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num-traits" version = "0.1.43" @@ -2462,6 +2594,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum glib-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "615bef979b5838526aee99241afc80cfb2e34a8735d4bcb8ec6072598c18a408" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" "checksum gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "70409d6405db8b1591602fcd0cbe8af52cd9976dd39194442b4c149ba343f86d" +"checksum gstreamer 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "90d7cde9eae8f6bf4d41c254915976b236143935ec2c0b027636274e998e54b4" +"checksum gstreamer-base 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05ec7a84b4160b61c72ea27ccf3f46eb9c8f996c5991746623e69e3e532e3cb5" +"checksum gstreamer-base-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "501a7add44f256aab6cb5b65ef121c449197cf55087d6a7586846c8d1e42e88b" +"checksum gstreamer-player 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d831e2c4aa1296a8d0f3b4caa0b5ae8d6f5c0eed67ed20236f8647010005c63e" +"checksum gstreamer-player-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3b9476078cc76164446e88b2c4331e81e24a07f7b7c3a8b4bf8975a47998ebd4" +"checksum gstreamer-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b2f51e25a6f97dd4bfd640cba96f192f8759b8766afd66d6d9ea0f82ca14a37" +"checksum gstreamer-video 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "76c6c8971688f530ae93e96ea29fe6051658bb4d00b4b40d30575ca1d8a25a18" +"checksum gstreamer-video-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ed798787e78a0f1c8be06bd3adcab03f962f049a820743aae9f690f56a0d538" "checksum gtk 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d695d6be4110618a97c19cd068e8a00e53e33b87e3c65cdc5397667498b1bc24" "checksum gtk-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d9554cf5b3a85a13fb39258c65b04b262989c1d7a758f8f555b77a478621a91" "checksum handlebars 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e7bdb08e879b8c78ee90f5022d121897c31ea022cb0cc6d13f2158c7a9fbabb1" @@ -2503,11 +2643,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum mime_guess 2.0.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130ea3c9c1b65dba905ab5a4d9ac59234a9585c24d135f264e187fe7336febbd" "checksum mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)" = "6d771e3ef92d58a8da8df7d6976bfca9371ed1de6619d9d5a5ce5b1f29b85bfe" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +"checksum muldiv 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1cbef5aa2e8cd82a18cc20e26434cc9843e1ef46e55bfabe5bddb022236c5b3e" "checksum native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f74dbadc8b43df7864539cedb7bc91345e532fdd913cfdc23ad94f4d2d40fbc0" "checksum net2 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)" = "9044faf1413a1057267be51b5afba8eb1090bd2231c693664aa1db716fe1eae0" "checksum new_debug_unreachable 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0cdc457076c78ab54d5e0d6fa7c47981757f1e34dc39ff92787f217dede586c4" "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" "checksum num-integer 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "6ac0ea58d64a89d9d6b7688031b3be9358d6c919badcf7fbb0527ccfd891ee45" +"checksum num-rational 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e" "checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" "checksum num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "775393e285254d2f5004596d69bb8bc1149754570dcc08cf30cabeba67955e28" "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" diff --git a/hammond-gtk/Cargo.toml b/hammond-gtk/Cargo.toml index 76169ce..7619360 100644 --- a/hammond-gtk/Cargo.toml +++ b/hammond-gtk/Cargo.toml @@ -11,6 +11,8 @@ crossbeam-channel = "0.1.2" gdk = "0.8.0" gdk-pixbuf = "0.4.0" glib = "0.5.0" +gstreamer = "0.11.2" +gstreamer-player = "0.11.0" humansize = "1.1.0" lazy_static = "1.0.0" log = "0.4.1" diff --git a/hammond-gtk/src/app.rs b/hammond-gtk/src/app.rs index 3d9f4fb..711c004 100644 --- a/hammond-gtk/src/app.rs +++ b/hammond-gtk/src/app.rs @@ -5,6 +5,7 @@ use gio::{ SimpleAction, SimpleActionExt, }; use glib; +use gstreamer_player as gst; use gtk; use gtk::prelude::*; use gtk::SettingsExt as GtkSettingsExt; @@ -53,6 +54,7 @@ pub enum Action { MarkAllPlayerNotification(Arc), RemoveShow(Arc), ErrorNotification(String), + PlayEpisode(String) } #[derive(Debug)] @@ -127,6 +129,12 @@ impl App { window.show_all(); window.activate(); + let player = gst::Player::new(None, None); + player.connect_error(clone!(sender => move |_,err| { + // Not the most user friendly... + sender.send(Action::ErrorNotification(format!("Playback: {}", err))).ok(); + })); + gtk::timeout_add(50, clone!(sender, receiver => move || { // Uses receiver, content, header, sender, overlay match receiver.try_recv() { @@ -183,7 +191,12 @@ impl App { let notif = InAppNotification::new(&err, callback, || {}, UndoState::Hidden); notif.show(&overlay); - } + }, + Ok(Action::PlayEpisode(uri)) => { + // This must be a 'real' (file://) uri not a path + player.set_uri(&uri); + player.play(); + }, Err(_) => (), } diff --git a/hammond-gtk/src/main.rs b/hammond-gtk/src/main.rs index a102303..e5d6d6a 100644 --- a/hammond-gtk/src/main.rs +++ b/hammond-gtk/src/main.rs @@ -10,6 +10,8 @@ extern crate gdk; extern crate gdk_pixbuf; extern crate gio; extern crate glib; +extern crate gstreamer; +extern crate gstreamer_player; extern crate gtk; #[macro_use] @@ -42,6 +44,7 @@ extern crate url; use log::Level; +use gstreamer as gst; use gtk::prelude::*; // http://gtk-rs.org/tuto/closures @@ -80,6 +83,7 @@ fn main() { // TODO: make the the logger a cli -vv option loggerv::init_with_level(Level::Info).expect("Error initializing loggerv."); gtk::init().expect("Error initializing gtk."); + gst::init().expect("Error initializing gstreamer"); static_resource::init().expect("Something went wrong with the resource file initialization."); // Add custom style diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index b7cfde3..581eecd 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -1,3 +1,4 @@ +use gio::{File, FileExt}; use glib; use gtk; use gtk::prelude::*; @@ -7,6 +8,7 @@ use chrono::prelude::*; use crossbeam_channel::Sender; use failure::Error; use humansize::{file_size_opts as size_opts, FileSize}; +#[allow(unused_imports)] use open; use hammond_data::dbqueries; @@ -446,15 +448,24 @@ fn on_play_bttn_clicked( episode: &mut EpisodeWidgetQuery, sender: &Sender, ) -> Result<(), Error> { - open_uri(episode.rowid())?; - episode.set_played_now()?; + let uri = dbqueries::get_episode_local_uri_from_id(episode.rowid())? + .ok_or_else(|| format_err!("Expected Some found None."))?; + let p = Path::new(&uri); + if p.exists() { + info!("Opening {}", uri); + // uri is actually a path, convert it (hacky) + let uri = File::new_for_path(p).get_uri().expect("Bad file path"); + sender.send(Action::PlayEpisode(uri)).ok(); + } else { + bail!("File \"{}\" does not exist.", uri); + } widget.info.set_title(&episode); sender .send(Action::RefreshEpisodesViewBGR) .map_err(From::from) } - +/* fn open_uri(rowid: i32) -> Result<(), Error> { let uri = dbqueries::get_episode_local_uri_from_id(rowid)? .ok_or_else(|| format_err!("Expected Some found None."))?; @@ -468,7 +479,7 @@ fn open_uri(rowid: i32) -> Result<(), Error> { Ok(()) } - +*/ // Setup a callback that will update the progress bar. #[inline] #[cfg_attr(feature = "cargo-clippy", allow(if_same_then_else))] diff --git a/org.gnome.Hammond.json b/org.gnome.Hammond.json index 8e27d4f..6b305d8 100644 --- a/org.gnome.Hammond.json +++ b/org.gnome.Hammond.json @@ -20,6 +20,7 @@ "--share=ipc", "--socket=x11", "--socket=wayland", + "--socket=pulseaudio", "--talk-name=org.freedesktop.Desktop" ], "build-options" : { From 4afdc54914ab43d483dd3aaf4e6b30ba217c49f7 Mon Sep 17 00:00:00 2001 From: Zander Brown Date: Tue, 29 May 2018 19:09:54 +0100 Subject: [PATCH 02/45] Initial playback control area (not plumbed in) --- hammond-gtk/resources/gtk/playback.ui | 103 ++++++++++++++++++++++++++ hammond-gtk/resources/gtk/style.css | 4 + hammond-gtk/resources/resources.xml | 1 + hammond-gtk/src/app.rs | 22 ++++-- hammond-gtk/src/widgets/mod.rs | 2 + hammond-gtk/src/widgets/playback.rs | 21 ++++++ 6 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 hammond-gtk/resources/gtk/playback.ui create mode 100644 hammond-gtk/src/widgets/playback.rs diff --git a/hammond-gtk/resources/gtk/playback.ui b/hammond-gtk/resources/gtk/playback.ui new file mode 100644 index 0000000..0457d68 --- /dev/null +++ b/hammond-gtk/resources/gtk/playback.ui @@ -0,0 +1,103 @@ + + + + + + True + False + media-playback-start-symbolic + + + True + False + center + True + True + + + True + False + center + center + 5 + 5 + 5 + 5 + 64 + image-x-generic-symbolic + 6 + + + 3 + 0 + 2 + + + + + True + True + start + True + True + False + False + False + right + + + 1 + 1 + 2 + + + + + True + True + True + center + center + 5 + 5 + 5 + 5 + image1 + + + 0 + 0 + 2 + + + + + True + False + start + end + label + + + 1 + 0 + + + + + True + False + end + end + label + + + 2 + 0 + + + + + diff --git a/hammond-gtk/resources/gtk/style.css b/hammond-gtk/resources/gtk/style.css index b97847e..cfb0ecd 100644 --- a/hammond-gtk/resources/gtk/style.css +++ b/hammond-gtk/resources/gtk/style.css @@ -9,3 +9,7 @@ row:last-child { list, border { border-radius: 4px; } + +.playback { + border-top: 1px solid @borders; +} diff --git a/hammond-gtk/resources/resources.xml b/hammond-gtk/resources/resources.xml index 67b98ae..6f0dd7f 100644 --- a/hammond-gtk/resources/resources.xml +++ b/hammond-gtk/resources/resources.xml @@ -13,6 +13,7 @@ gtk/inapp_notif.ui gtk/menus.ui gtk/help-overlay.ui + gtk/playback.ui gtk/style.css diff --git a/hammond-gtk/src/app.rs b/hammond-gtk/src/app.rs index 711c004..3dd14cc 100644 --- a/hammond-gtk/src/app.rs +++ b/hammond-gtk/src/app.rs @@ -18,7 +18,7 @@ use settings::{self, WindowGeometry}; use stacks::{Content, PopulatedState}; use utils; use widgets::appnotif::{InAppNotification, UndoState}; -use widgets::{about_dialog, mark_all_notif, remove_show_notif}; +use widgets::{about_dialog, mark_all_notif, remove_show_notif, Playback}; use std::rc::Rc; use std::sync::Arc; @@ -69,10 +69,6 @@ impl App { let application = gtk::Application::new("org.gnome.Hammond", ApplicationFlags::empty()) .expect("Application Initialization failed..."); - // Weird magic I copy-pasted that sets the Application Name in the Shell. - glib::set_application_name("Hammond"); - glib::set_prgname(Some("Hammond")); - let cleanup_date = settings::get_cleanup_date(&settings); utils::cleanup(cleanup_date); @@ -103,6 +99,9 @@ impl App { Inhibit(false) })); + let wrap = gtk::Box::new(gtk::Orientation::Vertical, 0); + window.add(&wrap); + // Create a content instance let content = Rc::new(Content::new(sender.clone()).expect( @@ -120,7 +119,13 @@ impl App { overlay.add(&content.get_stack()); // Add the overlay to the main window - window.add(&overlay); + wrap.add(&overlay); + + let playback = Playback::new(); + + let reveal = gtk::Revealer::new(); + reveal.add(&playback.container); + wrap.add(&reveal); WindowGeometry::from_settings(&settings).apply(&window); @@ -301,6 +306,11 @@ impl App { } pub fn run(self) { + // Weird magic I copy-pasted that sets the Application Name in the Shell. + glib::set_application_name("Hammond"); + glib::set_prgname(Some("Hammond")); + // We need out own org.gnome.Hammon icon + gtk::Window::set_default_icon_name("multimedia-player"); ApplicationExtManual::run(&self.app_instance, &[]); } } diff --git a/hammond-gtk/src/widgets/mod.rs b/hammond-gtk/src/widgets/mod.rs index df7361d..6360040 100644 --- a/hammond-gtk/src/widgets/mod.rs +++ b/hammond-gtk/src/widgets/mod.rs @@ -5,6 +5,7 @@ mod episode; mod home_view; mod show; mod shows_view; +mod playback; pub use self::aboutdialog::about_dialog; pub use self::empty::EmptyView; @@ -13,3 +14,4 @@ pub use self::home_view::HomeView; pub use self::show::ShowWidget; pub use self::show::{mark_all_notif, remove_show_notif}; pub use self::shows_view::ShowsView; +pub use self::playback::Playback; diff --git a/hammond-gtk/src/widgets/playback.rs b/hammond-gtk/src/widgets/playback.rs new file mode 100644 index 0000000..7ec6d4e --- /dev/null +++ b/hammond-gtk/src/widgets/playback.rs @@ -0,0 +1,21 @@ +use gtk; + +#[derive(Debug, Clone)] +pub struct Playback { + pub container: gtk::Grid, +} + +impl Default for Playback { + fn default() -> Self { + let builder = gtk::Builder::new_from_resource("/org/gnome/Hammond/gtk/playback.ui"); + let container = builder.get_object("wrapper").unwrap(); + + Playback { container } + } +} + +impl Playback { + pub fn new() -> Playback { + Playback::default() + } +} From 9528160b030acccd84947d621cc04698227fba68 Mon Sep 17 00:00:00 2001 From: Zander Brown Date: Tue, 29 May 2018 22:38:56 +0100 Subject: [PATCH 03/45] Start hooking things up Still doesn't accept input --- hammond-gtk/resources/gtk/playback.ui | 13 ++--- hammond-gtk/src/app.rs | 30 ++++++++--- hammond-gtk/src/widgets/playback.rs | 77 ++++++++++++++++++++++++++- 3 files changed, 105 insertions(+), 15 deletions(-) diff --git a/hammond-gtk/resources/gtk/playback.ui b/hammond-gtk/resources/gtk/playback.ui index 0457d68..8f32958 100644 --- a/hammond-gtk/resources/gtk/playback.ui +++ b/hammond-gtk/resources/gtk/playback.ui @@ -28,7 +28,7 @@ 6 - 3 + 1 0 2 @@ -46,13 +46,14 @@ right - 1 + 2 1 2 - + + 60 True True True @@ -79,7 +80,7 @@ label - 1 + 2 0 @@ -92,7 +93,7 @@ label - 2 + 3 0 @@ -100,4 +101,4 @@ - + \ No newline at end of file diff --git a/hammond-gtk/src/app.rs b/hammond-gtk/src/app.rs index 3dd14cc..ada84ba 100644 --- a/hammond-gtk/src/app.rs +++ b/hammond-gtk/src/app.rs @@ -6,6 +6,7 @@ use gio::{ }; use glib; use gstreamer_player as gst; +use gstreamer::ClockTime; use gtk; use gtk::prelude::*; use gtk::SettingsExt as GtkSettingsExt; @@ -54,7 +55,10 @@ pub enum Action { MarkAllPlayerNotification(Arc), RemoveShow(Arc), ErrorNotification(String), - PlayEpisode(String) + PlayEpisode(String), + PlayerStateChanged(gst::PlayerState), + PlayerMediaChanged(Option, ClockTime), + PlayerPositionChanged(ClockTime), } #[derive(Debug)] @@ -121,11 +125,8 @@ impl App { // Add the overlay to the main window wrap.add(&overlay); - let playback = Playback::new(); - - let reveal = gtk::Revealer::new(); - reveal.add(&playback.container); - wrap.add(&reveal); + let playback = Rc::new(Playback::new()); + wrap.add(playback.get_widget()); WindowGeometry::from_settings(&settings).apply(&window); @@ -140,8 +141,20 @@ impl App { sender.send(Action::ErrorNotification(format!("Playback: {}", err))).ok(); })); + player.connect_state_changed(clone!(sender => move |_,state| { + sender.send(Action::PlayerStateChanged(state)).ok(); + })); + + player.connect_media_info_updated(clone!(sender => move |_,info| { + sender.send(Action::PlayerMediaChanged(info.get_title(), info.get_duration())).ok(); + })); + + player.connect_property_position_notify(clone!(sender => move |p| { + sender.send(Action::PlayerPositionChanged(p.get_position())).ok(); + })); + gtk::timeout_add(50, clone!(sender, receiver => move || { - // Uses receiver, content, header, sender, overlay + // Uses receiver, content, header, sender, overlay, playback match receiver.try_recv() { Ok(Action::RefreshAllViews) => content.update(), Ok(Action::RefreshShowsView) => content.update_shows_view(), @@ -202,6 +215,9 @@ impl App { player.set_uri(&uri); player.play(); }, + Ok(Action::PlayerStateChanged(state)) => playback.state_changed(state), + Ok(Action::PlayerMediaChanged(t, l)) => playback.media_changed(t, l), + Ok(Action::PlayerPositionChanged(t)) => playback.position_changed(t), Err(_) => (), } diff --git a/hammond-gtk/src/widgets/playback.rs b/hammond-gtk/src/widgets/playback.rs index 7ec6d4e..becef5b 100644 --- a/hammond-gtk/src/widgets/playback.rs +++ b/hammond-gtk/src/widgets/playback.rs @@ -1,16 +1,32 @@ +use gstreamer::ClockTime; +use gstreamer_player as gst; use gtk; +use gtk::prelude::*; #[derive(Debug, Clone)] pub struct Playback { - pub container: gtk::Grid, + reveal: gtk::Revealer, + container: gtk::Grid, + play: gtk::Button, + seek: gtk::Scale, + title: gtk::Label, + time: gtk::Label, } impl Default for Playback { fn default() -> Self { let builder = gtk::Builder::new_from_resource("/org/gnome/Hammond/gtk/playback.ui"); let container = builder.get_object("wrapper").unwrap(); + let play = builder.get_object("play").unwrap(); + let seek = builder.get_object("seek").unwrap(); + let title = builder.get_object("title").unwrap(); + let time = builder.get_object("time").unwrap(); - Playback { container } + let reveal = gtk::Revealer::new(); + reveal.set_no_show_all(true); + reveal.add(&container); + + Playback { reveal, container, play, seek, title, time } } } @@ -18,4 +34,61 @@ impl Playback { pub fn new() -> Playback { Playback::default() } + + pub fn set_icon(&self, icon: &str) { + let image = gtk::Image::new_from_icon_name(icon, gtk::IconSize::Button.into()); + self.play.set_image(Some(&image)); + } + + pub fn get_widget(&self) -> >k::Revealer { + &self.reveal + } + + pub fn state_changed(&self, state: gst::PlayerState) { + // Once the playback controls are shown they don't go + // away again so show them unconditionally + self.reveal.show(); + self.reveal.set_reveal_child(true); + match state { + gst::PlayerState::Buffering => { + println!("Buffering!!!!!!!"); + }, + gst::PlayerState::Stopped | gst::PlayerState::Paused => { + println!("Stopped/Paused"); + self.set_icon("media-playback-start-symbolic"); + }, + gst::PlayerState::Playing => { + println!("Playing"); + self.set_icon("media-playback-pause-symbolic"); + }, + _ => { + println!("Weird stuff"); + } + } + } + + pub fn media_changed(&self, title: Option, length: ClockTime) { + self.reveal.show(); + self.reveal.set_reveal_child(true); + if let Some(title) = title { + self.title.set_label(&title); + } else { + self.title.set_label(""); + } + if let Some(s) = length.seconds() { + let hours = s / 3600; + let s = s - (hours * 3600); + let mins = s / 60; + let s = s - (mins * 60); + // This is a little nasty + let t = format!("{}:{}:{}", hours, mins, s); + self.time.set_label(&t); + } else { + self.time.set_label(""); + } + } + + pub fn position_changed(&self, _pos: ClockTime) { + println!("Tada!"); + } } From 1142948945b23b93009016a4a46cf973020c7e2f Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 7 Jun 2018 19:42:07 +0300 Subject: [PATCH 04/45] Rework the player widget. --- hammond-gtk/resources/gtk/player_toolbar.ui | 257 ++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 hammond-gtk/resources/gtk/player_toolbar.ui diff --git a/hammond-gtk/resources/gtk/player_toolbar.ui b/hammond-gtk/resources/gtk/player_toolbar.ui new file mode 100644 index 0000000..87186be --- /dev/null +++ b/hammond-gtk/resources/gtk/player_toolbar.ui @@ -0,0 +1,257 @@ + + + + + + True + False + False + 1 + media-seek-forward-symbolic + 1 + + + True + False + 1 + media-playback-pause-symbolic + 1 + + + True + False + 1 + media-playback-start-symbolic + 1 + + + True + False + 1 + media-seek-backward-symbolic + 1 + + + True + False + slide-up + + + True + False + 0.5 + 0 + in + + + True + False + end + + + True + False + center + + + 42 + True + False + True + True + Previous + previous_image + True + + + False + False + 0 + + + + + 60 + True + False + True + True + Play + play_image + True + + + False + False + 1 + + + + + 60 + True + False + True + True + Play + pause_image + True + + + False + False + 2 + + + + + 42 + True + False + True + True + Next + ff_image + True + + + False + False + 3 + + + + + + 0 + + + + + True + False + 36 + image-x-generic-symbolic + + + 1 + + + + + True + False + vertical + + + True + False + Show Title + + + False + True + 0 + + + + + True + False + Episode Title + + + False + True + 1 + + + + + 2 + + + + + 0 + True + True + end + True + True + False + -1 + 0 + False + False + right + + + 3 + + + + + True + False + start + center + 6 + + + True + False + start + center + 0:00 + + + False + False + 0 + + + + + True + False + start + center + / + + + False + False + 1 + + + + + True + False + start + center + 0:00 + + + False + False + 2 + + + + + 4 + + + + + + + + + + + From 58f09ba150f88d840eef78870cb302bbd49bea08 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Tue, 12 Jun 2018 17:43:21 +0300 Subject: [PATCH 05/45] h-gtk: Bind the new player widget to code. --- hammond-gtk/resources/gtk/player_toolbar.ui | 50 +++++---- hammond-gtk/src/widgets/mod.rs | 1 + hammond-gtk/src/widgets/player.rs | 107 ++++++++++++++++++++ 3 files changed, 140 insertions(+), 18 deletions(-) create mode 100644 hammond-gtk/src/widgets/player.rs diff --git a/hammond-gtk/resources/gtk/player_toolbar.ui b/hammond-gtk/resources/gtk/player_toolbar.ui index 87186be..94e0493 100644 --- a/hammond-gtk/resources/gtk/player_toolbar.ui +++ b/hammond-gtk/resources/gtk/player_toolbar.ui @@ -129,26 +129,16 @@ - + True False - 36 - image-x-generic-symbolic - - - 1 - - - - - True - False - vertical + 6 - + True False - Show Title + 42 + image-x-generic-symbolic False @@ -157,10 +147,34 @@ - + True False - Episode Title + vertical + + + True + False + Show Title + + + False + True + 0 + + + + + True + False + Episode Title + + + False + True + 1 + + False @@ -170,7 +184,7 @@ - 2 + 1 diff --git a/hammond-gtk/src/widgets/mod.rs b/hammond-gtk/src/widgets/mod.rs index 6360040..946bb6d 100644 --- a/hammond-gtk/src/widgets/mod.rs +++ b/hammond-gtk/src/widgets/mod.rs @@ -6,6 +6,7 @@ mod home_view; mod show; mod shows_view; mod playback; +mod player; pub use self::aboutdialog::about_dialog; pub use self::empty::EmptyView; diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs new file mode 100644 index 0000000..7d6c8d3 --- /dev/null +++ b/hammond-gtk/src/widgets/player.rs @@ -0,0 +1,107 @@ +#![allow(warnings)] + +use gstreamer_player as gst; +use gtk; +use gtk::prelude::*; + +#[derive(Debug, Clone)] +struct PlayerInfo { + container: gtk::Box, + show: gtk::Label, + episode: gtk::Label, + cover: gtk::Image, +} + +#[derive(Debug, Clone)] +struct PlayerTimes { + container: gtk::Box, + progressed: gtk::Label, + duration: gtk::Label, + separator: gtk::Label, + scalebar: gtk::Scale, +} + +#[derive(Debug, Clone)] +// FIXME: This is a mock till stuff get sorted out. +enum PlayerState { + Playing, + Paused, + Ready, +} + +#[derive(Debug, Clone)] +struct PlayerControls { + container: gtk::Box, + play: gtk::Button, + pause: gtk::Button, + forward: gtk::Button, + rewind: gtk::Button, + state: PlayerState, +} + +#[derive(Debug, Clone)] +pub struct PlayerWidget { + player: gst::Player, + revealer: gtk::Revealer, + action_bar: gtk::ActionBar, + controls: PlayerControls, + timer: PlayerTimes, + info: PlayerInfo, +} + +impl Default for PlayerWidget { + fn default() -> Self { + let builder = gtk::Builder::new_from_resource("/org/gnome/Hammond/gtk/player_toolbar.ui"); + let player = gst::Player::new(None, None); + let revealer = builder.get_object("revealer").unwrap(); + let action_bar = builder.get_object("action_bar").unwrap(); + + let buttons = builder.get_object("buttons").unwrap(); + let play = builder.get_object("play_button").unwrap(); + let pause = builder.get_object("pause_button").unwrap(); + let forward = builder.get_object("ff_button").unwrap(); + let rewind = builder.get_object("rewind_button").unwrap(); + + let controls = PlayerControls { + container: buttons, + play, + pause, + forward, + rewind, + state: PlayerState::Ready, + }; + + let timer_container = builder.get_object("timer").unwrap(); + let progressed = builder.get_object("progress_time_label").unwrap(); + let duration = builder.get_object("total_duration").unwrap(); + let separator = builder.get_object("separator").unwrap(); + let scalebar = builder.get_object("seek").unwrap(); + let timer = PlayerTimes { + container: timer_container, + progressed, + duration, + separator, + scalebar, + }; + + let labels = builder.get_object("info").unwrap(); + let show = builder.get_object("show_label").unwrap(); + let episode = builder.get_object("episode_label").unwrap(); + let cover = builder.get_object("show_cover").unwrap(); + let info = PlayerInfo { + container: labels, + show, + episode, + cover, + }; + + PlayerWidget { + player, + revealer, + action_bar, + controls, + timer, + info, + } + } +} From 47f297c4950e5c006a7c8539affe1b087e2dce3d Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Wed, 13 Jun 2018 18:10:45 +0300 Subject: [PATCH 06/45] PlayerWidget: Intial draft of the the PlayerExt trait. --- hammond-gtk/src/widgets/player.rs | 62 ++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index 7d6c8d3..cf519e8 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -1,9 +1,22 @@ #![allow(warnings)] use gstreamer_player as gst; +use gstreamer::ClockTime; use gtk; use gtk::prelude::*; +use failure::Error; + +pub trait PlayerExt { + fn play(&self); + fn pause(&self); + fn seek(&self, position: ClockTime); + fn fast_forward(&self); + fn rewind(&self); + // TODO: change playback rate + // fn set_playback_rate(&self); +} + #[derive(Debug, Clone)] struct PlayerInfo { container: gtk::Box, @@ -36,7 +49,7 @@ struct PlayerControls { pause: gtk::Button, forward: gtk::Button, rewind: gtk::Button, - state: PlayerState, + // state: PlayerState, } #[derive(Debug, Clone)] @@ -68,7 +81,7 @@ impl Default for PlayerWidget { pause, forward, rewind, - state: PlayerState::Ready, + // state: PlayerState::Ready, }; let timer_container = builder.get_object("timer").unwrap(); @@ -105,3 +118,48 @@ impl Default for PlayerWidget { } } } + +impl PlayerWidget { + fn reveal(&self) { + self.revealer.show(); + self.revealer.set_reveal_child(true); + } +} + +impl PlayerExt for PlayerWidget { + fn play(&self) { + // assert the state is either ready or paused + // TODO: assert!() + + self.reveal(); + + self.controls.pause.hide(); + self.controls.play.show(); + + self.player.play(); + } + + fn pause(&self) { + // assert the state is paused + // TODO: assert!() + + self.controls.pause.show(); + self.controls.play.hide(); + + self.player.pause(); + } + + fn seek(&self, position: ClockTime) { + self.player.seek(position); + } + + // FIXME + fn rewind(&self) { + // self.seek() + } + + // FIXME + fn fast_forward(&self) { + // self.seek() + } +} From 5f92df97e6b2dee4bf3caa15bd90df75ebba469c Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Wed, 13 Jun 2018 22:38:28 +0300 Subject: [PATCH 07/45] PlayerWidget: Wire the widget to the GUI. This commit also removes the majority of the playback widget, though most of it's code will make it to the PlayerWidget once it starts to get wired to the gtreamer_plaer::Player. --- hammond-gtk/resources/gtk/player_toolbar.ui | 396 +++++++++----------- hammond-gtk/resources/resources.xml | 2 +- hammond-gtk/src/app.rs | 55 ++- hammond-gtk/src/widgets/mod.rs | 3 +- hammond-gtk/src/widgets/playback.rs | 94 ----- hammond-gtk/src/widgets/player.rs | 16 +- 6 files changed, 221 insertions(+), 345 deletions(-) delete mode 100644 hammond-gtk/src/widgets/playback.rs diff --git a/hammond-gtk/resources/gtk/player_toolbar.ui b/hammond-gtk/resources/gtk/player_toolbar.ui index 94e0493..57c623c 100644 --- a/hammond-gtk/resources/gtk/player_toolbar.ui +++ b/hammond-gtk/resources/gtk/player_toolbar.ui @@ -31,241 +31,219 @@ media-seek-backward-symbolic 1 - - True + False - slide-up + True + end - + True False - 0.5 - 0 - in + center - + + 42 + True + False + True + True + Previous + previous_image + True + + + False + False + 0 + + + + + 60 + False + True + True + Play + play_image + True + + + False + False + 1 + + + + + 60 + True + False + True + True + Play + pause_image + True + + + False + False + 2 + + + + + 42 + True + False + True + True + Next + ff_image + True + + + False + False + 3 + + + + + + 0 + + + + + True + False + 6 + + True False - end + 42 + image-x-generic-symbolic + + + False + True + 0 + + + + + True + False + vertical - + True False - center - - - 42 - True - False - True - True - Previous - previous_image - True - - - False - False - 0 - - - - - 60 - True - False - True - True - Play - play_image - True - - - False - False - 1 - - - - - 60 - True - False - True - True - Play - pause_image - True - - - False - False - 2 - - - - - 42 - True - False - True - True - Next - ff_image - True - - - False - False - 3 - - - + Show Title + False + True 0 - + True False - 6 - - - True - False - 42 - image-x-generic-symbolic - - - False - True - 0 - - - - - True - False - vertical - - - True - False - Show Title - - - False - True - 0 - - - - - True - False - Episode Title - - - False - True - 1 - - - - - False - True - 1 - - + Episode Title + False + True 1 - - - 0 - True - True - end - True - True - False - -1 - 0 - False - False - right - - - 3 - - - - - True - False - start - center - 6 - - - True - False - start - center - 0:00 - - - False - False - 0 - - - - - True - False - start - center - / - - - False - False - 1 - - - - - True - False - start - center - 0:00 - - - False - False - 2 - - - - - 4 - - - - - + + False + True + 1 + + + 1 + + + + + True + True + True + True + False + -1 + 0 + False + False + right + + + 3 + + + + + True + False + start + center + 6 + + + True + False + start + center + 0:00 + + + False + False + 0 + + + + + True + False + start + center + / + + + False + False + 1 + + + + + True + False + start + center + 0:00 + + + False + False + 2 + + + + + 4 + diff --git a/hammond-gtk/resources/resources.xml b/hammond-gtk/resources/resources.xml index 6f0dd7f..4925609 100644 --- a/hammond-gtk/resources/resources.xml +++ b/hammond-gtk/resources/resources.xml @@ -13,7 +13,7 @@ gtk/inapp_notif.ui gtk/menus.ui gtk/help-overlay.ui - gtk/playback.ui + gtk/player_toolbar.ui gtk/style.css diff --git a/hammond-gtk/src/app.rs b/hammond-gtk/src/app.rs index ada84ba..7d041f0 100644 --- a/hammond-gtk/src/app.rs +++ b/hammond-gtk/src/app.rs @@ -5,8 +5,6 @@ use gio::{ SimpleAction, SimpleActionExt, }; use glib; -use gstreamer_player as gst; -use gstreamer::ClockTime; use gtk; use gtk::prelude::*; use gtk::SettingsExt as GtkSettingsExt; @@ -19,7 +17,7 @@ use settings::{self, WindowGeometry}; use stacks::{Content, PopulatedState}; use utils; use widgets::appnotif::{InAppNotification, UndoState}; -use widgets::{about_dialog, mark_all_notif, remove_show_notif, Playback}; +use widgets::{about_dialog, mark_all_notif, remove_show_notif, PlayerWidget}; use std::rc::Rc; use std::sync::Arc; @@ -56,9 +54,6 @@ pub enum Action { RemoveShow(Arc), ErrorNotification(String), PlayEpisode(String), - PlayerStateChanged(gst::PlayerState), - PlayerMediaChanged(Option, ClockTime), - PlayerPositionChanged(ClockTime), } #[derive(Debug)] @@ -103,8 +98,6 @@ impl App { Inhibit(false) })); - let wrap = gtk::Box::new(gtk::Orientation::Vertical, 0); - window.add(&wrap); // Create a content instance let content = @@ -122,11 +115,17 @@ impl App { let overlay = gtk::Overlay::new(); overlay.add(&content.get_stack()); - // Add the overlay to the main window + let wrap = gtk::Box::new(gtk::Orientation::Vertical, 0); + // Add the overlay to the main Box wrap.add(&overlay); - let playback = Rc::new(Playback::new()); - wrap.add(playback.get_widget()); + // FIXME: this should have a ::new() method instead. + let player = PlayerWidget::default(); + // Add the player to the main Box + wrap.add(&player.action_bar); + // player.reveal(); + + window.add(&wrap); WindowGeometry::from_settings(&settings).apply(&window); @@ -135,23 +134,22 @@ impl App { window.show_all(); window.activate(); - let player = gst::Player::new(None, None); - player.connect_error(clone!(sender => move |_,err| { + // player.connect_error(clone!(sender => move |_,err| { // Not the most user friendly... - sender.send(Action::ErrorNotification(format!("Playback: {}", err))).ok(); - })); + // sender.send(Action::ErrorNotification(format!("Playback: {}", err))).ok(); + // })); - player.connect_state_changed(clone!(sender => move |_,state| { - sender.send(Action::PlayerStateChanged(state)).ok(); - })); + // player.connect_state_changed(clone!(sender => move |_,state| { + // sender.send(Action::PlayerStateChanged(state)).ok(); + // })); - player.connect_media_info_updated(clone!(sender => move |_,info| { - sender.send(Action::PlayerMediaChanged(info.get_title(), info.get_duration())).ok(); - })); + // player.connect_media_info_updated(clone!(sender => move |_,info| { + // sender.send(Action::PlayerMediaChanged(info.get_title(), info.get_duration())).ok(); + // })); - player.connect_property_position_notify(clone!(sender => move |p| { - sender.send(Action::PlayerPositionChanged(p.get_position())).ok(); - })); + // player.connect_property_position_notify(clone!(sender => move |p| { + // sender.send(Action::PlayerPositionChanged(p.get_position())).ok(); + // })); gtk::timeout_add(50, clone!(sender, receiver => move || { // Uses receiver, content, header, sender, overlay, playback @@ -210,14 +208,7 @@ impl App { || {}, UndoState::Hidden); notif.show(&overlay); }, - Ok(Action::PlayEpisode(uri)) => { - // This must be a 'real' (file://) uri not a path - player.set_uri(&uri); - player.play(); - }, - Ok(Action::PlayerStateChanged(state)) => playback.state_changed(state), - Ok(Action::PlayerMediaChanged(t, l)) => playback.media_changed(t, l), - Ok(Action::PlayerPositionChanged(t)) => playback.position_changed(t), + Ok(Action::PlayEpisode(_uri)) => (), Err(_) => (), } diff --git a/hammond-gtk/src/widgets/mod.rs b/hammond-gtk/src/widgets/mod.rs index 946bb6d..32992c1 100644 --- a/hammond-gtk/src/widgets/mod.rs +++ b/hammond-gtk/src/widgets/mod.rs @@ -5,7 +5,6 @@ mod episode; mod home_view; mod show; mod shows_view; -mod playback; mod player; pub use self::aboutdialog::about_dialog; @@ -15,4 +14,4 @@ pub use self::home_view::HomeView; pub use self::show::ShowWidget; pub use self::show::{mark_all_notif, remove_show_notif}; pub use self::shows_view::ShowsView; -pub use self::playback::Playback; +pub use self::player::PlayerWidget; diff --git a/hammond-gtk/src/widgets/playback.rs b/hammond-gtk/src/widgets/playback.rs deleted file mode 100644 index becef5b..0000000 --- a/hammond-gtk/src/widgets/playback.rs +++ /dev/null @@ -1,94 +0,0 @@ -use gstreamer::ClockTime; -use gstreamer_player as gst; -use gtk; -use gtk::prelude::*; - -#[derive(Debug, Clone)] -pub struct Playback { - reveal: gtk::Revealer, - container: gtk::Grid, - play: gtk::Button, - seek: gtk::Scale, - title: gtk::Label, - time: gtk::Label, -} - -impl Default for Playback { - fn default() -> Self { - let builder = gtk::Builder::new_from_resource("/org/gnome/Hammond/gtk/playback.ui"); - let container = builder.get_object("wrapper").unwrap(); - let play = builder.get_object("play").unwrap(); - let seek = builder.get_object("seek").unwrap(); - let title = builder.get_object("title").unwrap(); - let time = builder.get_object("time").unwrap(); - - let reveal = gtk::Revealer::new(); - reveal.set_no_show_all(true); - reveal.add(&container); - - Playback { reveal, container, play, seek, title, time } - } -} - -impl Playback { - pub fn new() -> Playback { - Playback::default() - } - - pub fn set_icon(&self, icon: &str) { - let image = gtk::Image::new_from_icon_name(icon, gtk::IconSize::Button.into()); - self.play.set_image(Some(&image)); - } - - pub fn get_widget(&self) -> >k::Revealer { - &self.reveal - } - - pub fn state_changed(&self, state: gst::PlayerState) { - // Once the playback controls are shown they don't go - // away again so show them unconditionally - self.reveal.show(); - self.reveal.set_reveal_child(true); - match state { - gst::PlayerState::Buffering => { - println!("Buffering!!!!!!!"); - }, - gst::PlayerState::Stopped | gst::PlayerState::Paused => { - println!("Stopped/Paused"); - self.set_icon("media-playback-start-symbolic"); - }, - gst::PlayerState::Playing => { - println!("Playing"); - self.set_icon("media-playback-pause-symbolic"); - }, - _ => { - println!("Weird stuff"); - } - } - } - - pub fn media_changed(&self, title: Option, length: ClockTime) { - self.reveal.show(); - self.reveal.set_reveal_child(true); - if let Some(title) = title { - self.title.set_label(&title); - } else { - self.title.set_label(""); - } - if let Some(s) = length.seconds() { - let hours = s / 3600; - let s = s - (hours * 3600); - let mins = s / 60; - let s = s - (mins * 60); - // This is a little nasty - let t = format!("{}:{}:{}", hours, mins, s); - self.time.set_label(&t); - } else { - self.time.set_label(""); - } - } - - pub fn position_changed(&self, _pos: ClockTime) { - println!("Tada!"); - } -} diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index cf519e8..cff2f83 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -25,6 +25,12 @@ struct PlayerInfo { cover: gtk::Image, } +impl PlayerInfo { + fn init(&self) -> Result<(), Error> { + unimplemented!() + } +} + #[derive(Debug, Clone)] struct PlayerTimes { container: gtk::Box, @@ -54,9 +60,8 @@ struct PlayerControls { #[derive(Debug, Clone)] pub struct PlayerWidget { + pub action_bar: gtk::ActionBar, player: gst::Player, - revealer: gtk::Revealer, - action_bar: gtk::ActionBar, controls: PlayerControls, timer: PlayerTimes, info: PlayerInfo, @@ -66,7 +71,6 @@ impl Default for PlayerWidget { fn default() -> Self { let builder = gtk::Builder::new_from_resource("/org/gnome/Hammond/gtk/player_toolbar.ui"); let player = gst::Player::new(None, None); - let revealer = builder.get_object("revealer").unwrap(); let action_bar = builder.get_object("action_bar").unwrap(); let buttons = builder.get_object("buttons").unwrap(); @@ -86,7 +90,7 @@ impl Default for PlayerWidget { let timer_container = builder.get_object("timer").unwrap(); let progressed = builder.get_object("progress_time_label").unwrap(); - let duration = builder.get_object("total_duration").unwrap(); + let duration = builder.get_object("total_duration_label").unwrap(); let separator = builder.get_object("separator").unwrap(); let scalebar = builder.get_object("seek").unwrap(); let timer = PlayerTimes { @@ -110,7 +114,6 @@ impl Default for PlayerWidget { PlayerWidget { player, - revealer, action_bar, controls, timer, @@ -121,8 +124,7 @@ impl Default for PlayerWidget { impl PlayerWidget { fn reveal(&self) { - self.revealer.show(); - self.revealer.set_reveal_child(true); + self.action_bar.show(); } } From 3baa69b43b7be6e6e9462fe35b70cf77bc291e88 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Wed, 13 Jun 2018 22:42:07 +0300 Subject: [PATCH 08/45] cargo fmt --- hammond-gtk/src/widgets/episode.rs | 26 +++++++++++++------------- hammond-gtk/src/widgets/mod.rs | 4 ++-- hammond-gtk/src/widgets/player.rs | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index 581eecd..3da53fc 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -465,21 +465,21 @@ fn on_play_bttn_clicked( .send(Action::RefreshEpisodesViewBGR) .map_err(From::from) } -/* -fn open_uri(rowid: i32) -> Result<(), Error> { - let uri = dbqueries::get_episode_local_uri_from_id(rowid)? - .ok_or_else(|| format_err!("Expected Some found None."))?; - if Path::new(&uri).exists() { - info!("Opening {}", uri); - open::that(&uri)?; - } else { - bail!("File \"{}\" does not exist.", uri); - } +// fn open_uri(rowid: i32) -> Result<(), Error> { +// let uri = dbqueries::get_episode_local_uri_from_id(rowid)? +// .ok_or_else(|| format_err!("Expected Some found None."))?; + +// if Path::new(&uri).exists() { +// info!("Opening {}", uri); +// open::that(&uri)?; +// } else { +// bail!("File \"{}\" does not exist.", uri); +// } + +// Ok(()) +// } - Ok(()) -} -*/ // Setup a callback that will update the progress bar. #[inline] #[cfg_attr(feature = "cargo-clippy", allow(if_same_then_else))] diff --git a/hammond-gtk/src/widgets/mod.rs b/hammond-gtk/src/widgets/mod.rs index 32992c1..820baf3 100644 --- a/hammond-gtk/src/widgets/mod.rs +++ b/hammond-gtk/src/widgets/mod.rs @@ -3,15 +3,15 @@ pub mod appnotif; mod empty; mod episode; mod home_view; +mod player; mod show; mod shows_view; -mod player; pub use self::aboutdialog::about_dialog; pub use self::empty::EmptyView; pub use self::episode::EpisodeWidget; pub use self::home_view::HomeView; +pub use self::player::PlayerWidget; pub use self::show::ShowWidget; pub use self::show::{mark_all_notif, remove_show_notif}; pub use self::shows_view::ShowsView; -pub use self::player::PlayerWidget; diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index cff2f83..d021fba 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -1,7 +1,7 @@ #![allow(warnings)] -use gstreamer_player as gst; use gstreamer::ClockTime; +use gstreamer_player as gst; use gtk; use gtk::prelude::*; From 55d94b1844e8873773e022e824452dd4d3cbbb70 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 14 Jun 2018 00:06:45 +0300 Subject: [PATCH 09/45] CI: Add gstreamer as a dep for the debian build. --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ddc1e76..313b895 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,7 +25,7 @@ variables: before_script: - apt-get update -yqq - - apt-get install -yqq --no-install-recommends build-essential libgtk-3-dev meson + - apt-get install -yqq --no-install-recommends build-essential libgtk-3-dev meson libgstreamer1.0-dev - mkdir -p .cargo_cache # Only stuff inside the repo directory can be cached From 039c3182aac29e8ec65050717e82586651222b6b Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 14 Jun 2018 00:18:56 +0300 Subject: [PATCH 10/45] h-gtk: Remove unused .ui file. --- hammond-gtk/resources/gtk/playback.ui | 104 -------------------------- 1 file changed, 104 deletions(-) delete mode 100644 hammond-gtk/resources/gtk/playback.ui diff --git a/hammond-gtk/resources/gtk/playback.ui b/hammond-gtk/resources/gtk/playback.ui deleted file mode 100644 index 8f32958..0000000 --- a/hammond-gtk/resources/gtk/playback.ui +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - True - False - media-playback-start-symbolic - - - True - False - center - True - True - - - True - False - center - center - 5 - 5 - 5 - 5 - 64 - image-x-generic-symbolic - 6 - - - 1 - 0 - 2 - - - - - True - True - start - True - True - False - False - False - right - - - 2 - 1 - 2 - - - - - 60 - True - True - True - center - center - 5 - 5 - 5 - 5 - image1 - - - 0 - 0 - 2 - - - - - True - False - start - end - label - - - 2 - 0 - - - - - True - False - end - end - label - - - 3 - 0 - - - - - \ No newline at end of file From ac7b1a3c66abf4fd36c7cc8f68e1b2036d5c9e05 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 14 Jun 2018 00:24:42 +0300 Subject: [PATCH 11/45] CI: disable debian builds fow now. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Debian stable provides gst 1.10 but the gst-rs bindings requiere v 1.12 to build. I will make custom images Soon™ --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 313b895..32c78c8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -41,12 +41,12 @@ variables: - cargo test -- --test-threads=1 --ignored <<: *cargo_cache -rust:stable: +.rust:stable: # https://hub.docker.com/_/rust/ image: "rust" <<: *cargo_test -rust:nightly: +.rust:nightly: # https://hub.docker.com/r/rustlang/rust/ image: "rustlang/rust:nightly" <<: *cargo_test @@ -129,7 +129,7 @@ rustfmt: # Configure and run clippy on nightly # Only fails on errors atm. -clippy: +.clippy: image: "registry.gitlab.gnome.org/alatiera/hammond-container-images/clippy:nightly" stage: lint variables: From 1b78d221b6bb2f23d6275b52bfc2ff898d5b088e Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 14 Jun 2018 02:17:43 +0300 Subject: [PATCH 12/45] PlayerWidget: Wire the play and pause buttons and add style classes to the Info Labels. This also includes the yak shaving of a ::new and ::inti methods. --- hammond-gtk/resources/gtk/player_toolbar.ui | 15 +++- hammond-gtk/resources/gtk/style.css | 9 ++- hammond-gtk/src/app.rs | 7 +- hammond-gtk/src/widgets/episode.rs | 24 ++---- hammond-gtk/src/widgets/player.rs | 81 +++++++++++++++++++-- 5 files changed, 103 insertions(+), 33 deletions(-) diff --git a/hammond-gtk/resources/gtk/player_toolbar.ui b/hammond-gtk/resources/gtk/player_toolbar.ui index 57c623c..1ae5620 100644 --- a/hammond-gtk/resources/gtk/player_toolbar.ui +++ b/hammond-gtk/resources/gtk/player_toolbar.ui @@ -39,7 +39,6 @@ True False - center 42 @@ -60,7 +59,6 @@ 60 - False True True Play @@ -77,7 +75,6 @@ 60 True - False True True Play @@ -124,7 +121,7 @@ True False - 42 + 24 image-x-generic-symbolic @@ -137,12 +134,18 @@ True False + True + center + center vertical True False Show Title + False @@ -155,6 +158,9 @@ True False Episode Title + False @@ -178,6 +184,7 @@ True True + center True True False diff --git a/hammond-gtk/resources/gtk/style.css b/hammond-gtk/resources/gtk/style.css index cfb0ecd..b4d2284 100644 --- a/hammond-gtk/resources/gtk/style.css +++ b/hammond-gtk/resources/gtk/style.css @@ -10,6 +10,11 @@ list, border { border-radius: 4px; } -.playback { - border-top: 1px solid @borders; +.player-show-label { + font-weight: bold; + font-size: smaller; } + +.player-episode-label { + font-size: smaller; +} \ No newline at end of file diff --git a/hammond-gtk/src/app.rs b/hammond-gtk/src/app.rs index 7d041f0..27e7d2e 100644 --- a/hammond-gtk/src/app.rs +++ b/hammond-gtk/src/app.rs @@ -53,7 +53,7 @@ pub enum Action { MarkAllPlayerNotification(Arc), RemoveShow(Arc), ErrorNotification(String), - PlayEpisode(String), + InitEpisode(i32), } #[derive(Debug)] @@ -119,8 +119,7 @@ impl App { // Add the overlay to the main Box wrap.add(&overlay); - // FIXME: this should have a ::new() method instead. - let player = PlayerWidget::default(); + let player = PlayerWidget::new(); // Add the player to the main Box wrap.add(&player.action_bar); // player.reveal(); @@ -208,7 +207,7 @@ impl App { || {}, UndoState::Hidden); notif.show(&overlay); }, - Ok(Action::PlayEpisode(_uri)) => (), + Ok(Action::InitEpisode(rowid)) => player.initialize_episode(rowid).unwrap(), Err(_) => (), } diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index 3da53fc..1af0531 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -1,4 +1,3 @@ -use gio::{File, FileExt}; use glib; use gtk; use gtk::prelude::*; @@ -18,7 +17,6 @@ use hammond_data::EpisodeWidgetQuery; use app::Action; use manager; -use std::path::Path; use std::rc::Rc; use std::sync::{Arc, Mutex, TryLockError}; @@ -444,26 +442,18 @@ fn on_download_clicked(ep: &EpisodeWidgetQuery, sender: &Sender) -> Resu } fn on_play_bttn_clicked( - widget: &Rc, + _widget: &Rc, episode: &mut EpisodeWidgetQuery, sender: &Sender, ) -> Result<(), Error> { - let uri = dbqueries::get_episode_local_uri_from_id(episode.rowid())? - .ok_or_else(|| format_err!("Expected Some found None."))?; - let p = Path::new(&uri); - if p.exists() { - info!("Opening {}", uri); - // uri is actually a path, convert it (hacky) - let uri = File::new_for_path(p).get_uri().expect("Bad file path"); - sender.send(Action::PlayEpisode(uri)).ok(); - } else { - bail!("File \"{}\" does not exist.", uri); - } - - widget.info.set_title(&episode); sender - .send(Action::RefreshEpisodesViewBGR) + .send(Action::InitEpisode(episode.rowid())) .map_err(From::from) + + // widget.info.set_title(&episode); + // sender + // .send(Action::RefreshEpisodesViewBGR) + // .map_err(From::from) } // fn open_uri(rowid: i32) -> Result<(), Error> { diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index d021fba..1531b27 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -1,5 +1,7 @@ #![allow(warnings)] +use gio::{File, FileExt}; + use gstreamer::ClockTime; use gstreamer_player as gst; use gtk; @@ -7,6 +9,14 @@ use gtk::prelude::*; use failure::Error; +use hammond_data::dbqueries; +use hammond_data::{EpisodeWidgetQuery, PodcastCoverQuery}; + +use utils::set_image_from_path; + +use std::path::Path; +use std::rc::Rc; + pub trait PlayerExt { fn play(&self); fn pause(&self); @@ -26,8 +36,25 @@ struct PlayerInfo { } impl PlayerInfo { - fn init(&self) -> Result<(), Error> { - unimplemented!() + // FIXME: create a Diesel Model of the joined episode and podcast query instead + fn init(&self, episode: &EpisodeWidgetQuery, podcast: &PodcastCoverQuery) { + self.set_cover_image(podcast); + self.set_show_title(podcast); + self.set_episode_title(episode); + } + + fn set_episode_title(&self, episode: &EpisodeWidgetQuery) { + self.episode.set_text(&episode.title()); + } + + fn set_show_title(&self, show: &PodcastCoverQuery) { + self.show.set_text(&show.title()); + } + + fn set_cover_image(&self, show: &PodcastCoverQuery) { + set_image_from_path(&self.cover, show.id(), 24) + .map_err(|err| error!("Player Cover: {}", err)) + .ok(); } } @@ -123,9 +150,51 @@ impl Default for PlayerWidget { } impl PlayerWidget { + pub fn new() -> Rc { + let w = Rc::new(Self::default()); + Self::init(&w); + w + } + + fn init(s: &Rc) { + // Connect the play button to the gst Player. + s.controls.play.connect_clicked(clone!(s => move |_| s.play())); + + // Connect the pause button to the gst Player. + s.controls.pause.connect_clicked(clone!(s => move |_| s.pause())); + } + fn reveal(&self) { self.action_bar.show(); } + + pub fn initialize_episode(&self, rowid: i32) -> Result<(), Error> { + let ep = dbqueries::get_episode_widget_from_rowid(rowid)?; + let pd = dbqueries::get_podcast_cover_from_id(ep.podcast_id())?; + + self.info.init(&ep, &pd); + // Currently that will always be the case since the play button is + // only shown if the file is downloaded + if let Some(ref path) = ep.local_uri() { + if Path::new(path).exists() { + // path is an absolute fs path ex. "foo/bar/baz". + // Convert it so it will have a "file:///" + // FIXME: convert it properly + let uri = File::new_for_path(path).get_uri().expect("Bad file path"); + + // FIXME: Should also reset/flush the pipeline and then add the file + + // play the file + self.player.set_uri(&uri); + self.play(); + return Ok(()); + } + // TODO: log an error + } + + // Stream stuff + unimplemented!() + } } impl PlayerExt for PlayerWidget { @@ -135,8 +204,8 @@ impl PlayerExt for PlayerWidget { self.reveal(); - self.controls.pause.hide(); - self.controls.play.show(); + self.controls.pause.show(); + self.controls.play.hide(); self.player.play(); } @@ -145,8 +214,8 @@ impl PlayerExt for PlayerWidget { // assert the state is paused // TODO: assert!() - self.controls.pause.show(); - self.controls.play.hide(); + self.controls.pause.hide(); + self.controls.play.show(); self.player.pause(); } From a7b639a66be589021d60badd0919252f5b7c352f Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 14 Jun 2018 04:39:29 +0300 Subject: [PATCH 13/45] PlayerWidget: Wire the PlayerTimes labels and scale. Adapted from gstreamer basic-tutorial-5. https://gstreamer.freedesktop.org/documentation/tutorials/basic/toolkit-integration.html --- hammond-gtk/resources/gtk/player_toolbar.ui | 10 +--- hammond-gtk/src/widgets/player.rs | 63 +++++++++++++++++++-- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/hammond-gtk/resources/gtk/player_toolbar.ui b/hammond-gtk/resources/gtk/player_toolbar.ui index 1ae5620..b4c6cd4 100644 --- a/hammond-gtk/resources/gtk/player_toolbar.ui +++ b/hammond-gtk/resources/gtk/player_toolbar.ui @@ -184,18 +184,12 @@ True True - center True - True - False - -1 - 0 + 1 False - False - right - 3 + 2 diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index 1531b27..6e47555 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -2,8 +2,10 @@ use gio::{File, FileExt}; +use gst::prelude::*; +use gstreamer as gst; use gstreamer::ClockTime; -use gstreamer_player as gst; +use gstreamer_player as gst_player; use gtk; use gtk::prelude::*; @@ -88,7 +90,7 @@ struct PlayerControls { #[derive(Debug, Clone)] pub struct PlayerWidget { pub action_bar: gtk::ActionBar, - player: gst::Player, + player: gst_player::Player, controls: PlayerControls, timer: PlayerTimes, info: PlayerInfo, @@ -97,7 +99,7 @@ pub struct PlayerWidget { impl Default for PlayerWidget { fn default() -> Self { let builder = gtk::Builder::new_from_resource("/org/gnome/Hammond/gtk/player_toolbar.ui"); - let player = gst::Player::new(None, None); + let player = gst_player::Player::new(None, None); let action_bar = builder.get_object("action_bar").unwrap(); let buttons = builder.get_object("buttons").unwrap(); @@ -119,7 +121,8 @@ impl Default for PlayerWidget { let progressed = builder.get_object("progress_time_label").unwrap(); let duration = builder.get_object("total_duration_label").unwrap(); let separator = builder.get_object("separator").unwrap(); - let scalebar = builder.get_object("seek").unwrap(); + let scalebar: gtk::Scale = builder.get_object("seek").unwrap(); + scalebar.set_range(0.0, 1.0); let timer = PlayerTimes { container: timer_container, progressed, @@ -156,12 +159,15 @@ impl PlayerWidget { w } + #[cfg_attr(rustfmt, rustfmt_skip)] fn init(s: &Rc) { // Connect the play button to the gst Player. s.controls.play.connect_clicked(clone!(s => move |_| s.play())); // Connect the pause button to the gst Player. s.controls.pause.connect_clicked(clone!(s => move |_| s.pause())); + + Self::connect_timers(s); } fn reveal(&self) { @@ -195,6 +201,55 @@ impl PlayerWidget { // Stream stuff unimplemented!() } + + // FIXME: Refactor to use gst_player::Player instead of raw pipeline. + // FIXME: Refactor the labels to use some kind of Human™ time/values. + // Adapted from https://github.com/sdroege/gstreamer-rs/blob/f4d57a66522183d4927b47af422e8f321182111f/tutorials/src/bin/basic-tutorial-5.rs#L131-L164 + fn connect_timers(s: &Rc) { + let slider_update_signal_id = s.timer.scalebar.connect_value_changed( + clone!(s => move |slider| { + let pipeline = &s.player.get_pipeline(); + + let value = slider.get_value() as u64; + if let Err(_) = pipeline.seek_simple( + gst::SeekFlags::FLUSH | gst::SeekFlags::KEY_UNIT, + value * gst::SECOND, + ) { + error!("Seeking to {} failed", value); + } + }), + ); + + // Update the PlayerTimes + gtk::timeout_add_seconds( + 1, + clone!(s => move || { + let pipeline = s.player.get_pipeline(); + let slider = &s.timer.scalebar; + + if let Some(dur) = pipeline.query_duration::() { + let seconds = dur / gst::SECOND; + let seconds = seconds.map(|v| v as f64).unwrap_or(0.0); + + slider.set_range(0.0, seconds); + s.timer.duration.set_text(&format!("{:.2}", seconds / 60.0)); + } + + if let Some(pos) = pipeline.query_position::() { + let seconds = pos / gst::SECOND; + let seconds = seconds.map(|v| v as f64).unwrap_or(0.0); + + slider.block_signal(&slider_update_signal_id); + slider.set_value(seconds); + slider.unblock_signal(&slider_update_signal_id); + + s.timer.progressed.set_text(&format!("{:.2}", seconds / 60.0)); + } + + Continue(true) + }), + ); + } } impl PlayerExt for PlayerWidget { From 76720424ab5fde4949e94f6a0fe119ef512eb24d Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 14 Jun 2018 09:24:24 +0300 Subject: [PATCH 14/45] PlayerWidget: Set a custom config for the gst Player. --- hammond-data/src/lib.rs | 4 +++- hammond-gtk/src/widgets/player.rs | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/hammond-data/src/lib.rs b/hammond-data/src/lib.rs index 7143c84..d1a617c 100644 --- a/hammond-data/src/lib.rs +++ b/hammond-data/src/lib.rs @@ -85,7 +85,9 @@ pub use models::{Episode, EpisodeWidgetQuery, Podcast, PodcastCoverQuery, Source // Set the user agent, See #53 for more // Keep this in sync with Tor-browser releases -const USER_AGENT: &str = "Mozilla/5.0 (Windows NT 6.1; rv:52.0) Gecko/20100101 Firefox/52.0"; +/// The user-agent to be used for all the requests. +/// It originates from the Tor-browser UA. +pub const USER_AGENT: &str = "Mozilla/5.0 (Windows NT 6.1; rv:52.0) Gecko/20100101 Firefox/52.0"; /// [XDG Base Direcotory](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) Paths. #[allow(missing_debug_implementations)] diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index 6e47555..c81eb92 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -11,7 +11,7 @@ use gtk::prelude::*; use failure::Error; -use hammond_data::dbqueries; +use hammond_data::{dbqueries, USER_AGENTR}; use hammond_data::{EpisodeWidgetQuery, PodcastCoverQuery}; use utils::set_image_from_path; @@ -98,8 +98,14 @@ pub struct PlayerWidget { impl Default for PlayerWidget { fn default() -> Self { - let builder = gtk::Builder::new_from_resource("/org/gnome/Hammond/gtk/player_toolbar.ui"); let player = gst_player::Player::new(None, None); + + let mut config = player.get_config(); + config.set_user_agent(USER_AGENT); + config.set_position_update_interval(250); + player.set_config(config).unwrap(); + + let builder = gtk::Builder::new_from_resource("/org/gnome/Hammond/gtk/player_toolbar.ui"); let action_bar = builder.get_object("action_bar").unwrap(); let buttons = builder.get_object("buttons").unwrap(); From a9f81d0ad39a102c72415d497785cfba52508bb7 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 14 Jun 2018 09:54:35 +0300 Subject: [PATCH 15/45] PlayerWidget: Refactor the timers callbacks. Should use the gst_player::Player callbacks instead but they require the Send Trait which means we would need to use SendCell and that's not something I am going to deal with right now. --- hammond-gtk/src/widgets/player.rs | 68 +++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index c81eb92..204cda5 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -2,6 +2,7 @@ use gio::{File, FileExt}; +use glib::SignalHandlerId; use gst::prelude::*; use gstreamer as gst; use gstreamer::ClockTime; @@ -11,7 +12,7 @@ use gtk::prelude::*; use failure::Error; -use hammond_data::{dbqueries, USER_AGENTR}; +use hammond_data::{dbqueries, USER_AGENT}; use hammond_data::{EpisodeWidgetQuery, PodcastCoverQuery}; use utils::set_image_from_path; @@ -194,7 +195,7 @@ impl PlayerWidget { // FIXME: convert it properly let uri = File::new_for_path(path).get_uri().expect("Bad file path"); - // FIXME: Should also reset/flush the pipeline and then add the file + // FIXME: Maybe should also reset/flush the pipeline and then add the file? // play the file self.player.set_uri(&uri); @@ -227,35 +228,58 @@ impl PlayerWidget { ); // Update the PlayerTimes - gtk::timeout_add_seconds( - 1, + gtk::timeout_add( + 250, clone!(s => move || { let pipeline = s.player.get_pipeline(); let slider = &s.timer.scalebar; - if let Some(dur) = pipeline.query_duration::() { - let seconds = dur / gst::SECOND; - let seconds = seconds.map(|v| v as f64).unwrap_or(0.0); - - slider.set_range(0.0, seconds); - s.timer.duration.set_text(&format!("{:.2}", seconds / 60.0)); - } - - if let Some(pos) = pipeline.query_position::() { - let seconds = pos / gst::SECOND; - let seconds = seconds.map(|v| v as f64).unwrap_or(0.0); - - slider.block_signal(&slider_update_signal_id); - slider.set_value(seconds); - slider.unblock_signal(&slider_update_signal_id); - - s.timer.progressed.set_text(&format!("{:.2}", seconds / 60.0)); - } + // TODO: use Player::connect_duration_changed() instead + s.on_duration_changed(&slider_update_signal_id); + // TODO: use Player::connect_position_updated() instead + s.on_position_changed(&slider_update_signal_id); Continue(true) }), ); } + + /// Update the duration `gtk::Label` and the max range of the `gtk::SclaeBar`. + fn on_duration_changed(&self, slider_update: &SignalHandlerId) { + let pipeline = &self.player.get_pipeline(); + let slider = &self.timer.scalebar; + + if let Some(dur) = pipeline.query_duration::() { + let seconds = dur / gst::SECOND; + let seconds = seconds.map(|v| v as f64).unwrap_or(0.0); + + slider.block_signal(&slider_update); + slider.set_range(0.0, seconds); + slider.unblock_signal(&slider_update); + self.timer + .duration + .set_text(&format!("{:.2}", seconds / 60.0)); + } + } + + /// Update the `gtk::SclaeBar` when the pipeline position is changed.. + fn on_position_changed(&self, slider_update: &SignalHandlerId) { + let pipeline = &self.player.get_pipeline(); + let slider = &self.timer.scalebar; + + if let Some(pos) = pipeline.query_position::() { + let seconds = pos / gst::SECOND; + let seconds = seconds.map(|v| v as f64).unwrap_or(0.0); + + slider.block_signal(&slider_update); + slider.set_value(seconds); + slider.unblock_signal(&slider_update); + + self.timer + .progressed + .set_text(&format!("{:.2}", seconds / 60.0)); + } + } } impl PlayerExt for PlayerWidget { From 1daa841f312aa2e2d33d24d3930ce47e05a5e425 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 14 Jun 2018 10:07:25 +0300 Subject: [PATCH 16/45] PlayerWidget: Connect to the errors callback. --- hammond-gtk/src/app.rs | 2 +- hammond-gtk/src/widgets/player.rs | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/hammond-gtk/src/app.rs b/hammond-gtk/src/app.rs index 27e7d2e..6573f98 100644 --- a/hammond-gtk/src/app.rs +++ b/hammond-gtk/src/app.rs @@ -119,7 +119,7 @@ impl App { // Add the overlay to the main Box wrap.add(&overlay); - let player = PlayerWidget::new(); + let player = PlayerWidget::new(&sender); // Add the player to the main Box wrap.add(&player.action_bar); // player.reveal(); diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index 204cda5..5e29424 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -10,11 +10,13 @@ use gstreamer_player as gst_player; use gtk; use gtk::prelude::*; +use crossbeam_channel::Sender; use failure::Error; use hammond_data::{dbqueries, USER_AGENT}; use hammond_data::{EpisodeWidgetQuery, PodcastCoverQuery}; +use app::Action; use utils::set_image_from_path; use std::path::Path; @@ -160,20 +162,28 @@ impl Default for PlayerWidget { } impl PlayerWidget { - pub fn new() -> Rc { + pub fn new(sender: &Sender) -> Rc { let w = Rc::new(Self::default()); - Self::init(&w); + Self::init(&w, sender); w } #[cfg_attr(rustfmt, rustfmt_skip)] - fn init(s: &Rc) { + fn init(s: &Rc, sender: &Sender) { // Connect the play button to the gst Player. s.controls.play.connect_clicked(clone!(s => move |_| s.play())); // Connect the pause button to the gst Player. s.controls.pause.connect_clicked(clone!(s => move |_| s.pause())); + s.player.connect_error(clone!(sender => move |_, error| { + // FIXME: should never occur and should not be user facing. + sender.send(Action::ErrorNotification(format!("Player Error: {}", error))) + .map_err(|err| error!("Error: {}", err)) + .ok(); + + })); + Self::connect_timers(s); } From 38768c777d80d656ca7f80c6f4057a7f750e09d4 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 14 Jun 2018 10:32:41 +0300 Subject: [PATCH 17/45] PlayerWidget: Connect the fast-forward and rewind buttons, sort of. There appears to be a bug where it seeks 17 seconds instead of 10. --- hammond-gtk/resources/gtk/player_toolbar.ui | 2 - hammond-gtk/src/widgets/player.rs | 55 +++++++++++++++++---- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/hammond-gtk/resources/gtk/player_toolbar.ui b/hammond-gtk/resources/gtk/player_toolbar.ui index b4c6cd4..5e246a6 100644 --- a/hammond-gtk/resources/gtk/player_toolbar.ui +++ b/hammond-gtk/resources/gtk/player_toolbar.ui @@ -43,7 +43,6 @@ 42 True - False True True Previous @@ -91,7 +90,6 @@ 42 True - False True True Next diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index 5e29424..974fdc3 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -22,10 +22,16 @@ use utils::set_image_from_path; use std::path::Path; use std::rc::Rc; +#[derive(Debug, Clone, Copy)] +pub enum SeekDirection { + Backwards, + Forward, +} + pub trait PlayerExt { fn play(&self); fn pause(&self); - fn seek(&self, position: ClockTime); + fn seek(&self, offset: ClockTime, direction: SeekDirection); fn fast_forward(&self); fn rewind(&self); // TODO: change playback rate @@ -106,6 +112,7 @@ impl Default for PlayerWidget { let mut config = player.get_config(); config.set_user_agent(USER_AGENT); config.set_position_update_interval(250); + config.set_seek_accurate(true); player.set_config(config).unwrap(); let builder = gtk::Builder::new_from_resource("/org/gnome/Hammond/gtk/player_toolbar.ui"); @@ -176,6 +183,12 @@ impl PlayerWidget { // Connect the pause button to the gst Player. s.controls.pause.connect_clicked(clone!(s => move |_| s.pause())); + // Connect the rewind button to the gst Player. + s.controls.rewind.connect_clicked(clone!(s => move |_| s.rewind())); + + // Connect the fast-forward button to the gst Player. + s.controls.forward.connect_clicked(clone!(s => move |_| s.fast_forward())); + s.player.connect_error(clone!(sender => move |_, error| { // FIXME: should never occur and should not be user facing. sender.send(Action::ErrorNotification(format!("Player Error: {}", error))) @@ -259,7 +272,7 @@ impl PlayerWidget { let pipeline = &self.player.get_pipeline(); let slider = &self.timer.scalebar; - if let Some(dur) = pipeline.query_duration::() { + if let Some(dur) = pipeline.query_duration::() { let seconds = dur / gst::SECOND; let seconds = seconds.map(|v| v as f64).unwrap_or(0.0); @@ -277,7 +290,7 @@ impl PlayerWidget { let pipeline = &self.player.get_pipeline(); let slider = &self.timer.scalebar; - if let Some(pos) = pipeline.query_position::() { + if let Some(pos) = pipeline.query_position::() { let seconds = pos / gst::SECOND; let seconds = seconds.map(|v| v as f64).unwrap_or(0.0); @@ -315,17 +328,41 @@ impl PlayerExt for PlayerWidget { self.player.pause(); } - fn seek(&self, position: ClockTime) { - self.player.seek(position); + // Adapted from https://github.com/philn/glide/blob/b52a65d99daeab0b487f79a0e1ccfad0cd433e22/src/player_context.rs#L219-L245 + fn seek(&self, offset: ClockTime, direction: SeekDirection) { + let position = self.player.get_position(); + if position == ClockTime::none() { + return; + } + + let destination = match direction { + SeekDirection::Backwards => { + if position >= offset { + Some(position - offset) + } else { + None + } + } + SeekDirection::Forward => { + let duration = self.player.get_duration(); + if duration != ClockTime::none() && position + offset <= duration { + Some(position + offset) + } else { + None + } + } + }; + + destination.map(|d| self.player.seek(d)); } - // FIXME + // FIXME: make the interval a GSetting fn rewind(&self) { - // self.seek() + self.seek(ClockTime::from_seconds(10), SeekDirection::Backwards) } - // FIXME + // FIXME: make the interval a GSetting fn fast_forward(&self) { - // self.seek() + self.seek(ClockTime::from_seconds(10), SeekDirection::Forward) } } From 48d80d31941e65dc4ac4c2341965a7c052725bce Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 14 Jun 2018 10:55:50 +0300 Subject: [PATCH 18/45] PlayerWidget: Remove unused vars an Enum. --- hammond-gtk/src/widgets/player.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index 974fdc3..c9e2416 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -1,5 +1,3 @@ -#![allow(warnings)] - use gio::{File, FileExt}; use glib::SignalHandlerId; @@ -78,14 +76,6 @@ struct PlayerTimes { scalebar: gtk::Scale, } -#[derive(Debug, Clone)] -// FIXME: This is a mock till stuff get sorted out. -enum PlayerState { - Playing, - Paused, - Ready, -} - #[derive(Debug, Clone)] struct PlayerControls { container: gtk::Box, @@ -93,7 +83,6 @@ struct PlayerControls { pause: gtk::Button, forward: gtk::Button, rewind: gtk::Button, - // state: PlayerState, } #[derive(Debug, Clone)] @@ -130,7 +119,6 @@ impl Default for PlayerWidget { pause, forward, rewind, - // state: PlayerState::Ready, }; let timer_container = builder.get_object("timer").unwrap(); @@ -254,9 +242,6 @@ impl PlayerWidget { gtk::timeout_add( 250, clone!(s => move || { - let pipeline = s.player.get_pipeline(); - let slider = &s.timer.scalebar; - // TODO: use Player::connect_duration_changed() instead s.on_duration_changed(&slider_update_signal_id); // TODO: use Player::connect_position_updated() instead From 8e4b705e60c2e0f29dcb840a45901d7a05e7bd2e Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 15 Jun 2018 13:02:27 +0300 Subject: [PATCH 19/45] PlayerWidget: Log the gst warnings. --- hammond-gtk/src/widgets/player.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index c9e2416..9dd5c3a 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -177,6 +177,10 @@ impl PlayerWidget { // Connect the fast-forward button to the gst Player. s.controls.forward.connect_clicked(clone!(s => move |_| s.fast_forward())); + // Log gst warnings. + s.player.connect_warning(move |_, warn| warn!("gst warning: {}", warn)); + + // Log gst errors. s.player.connect_error(clone!(sender => move |_, error| { // FIXME: should never occur and should not be user facing. sender.send(Action::ErrorNotification(format!("Player Error: {}", error))) From 6c3fbfe0ca77614fd87470446fc64c0919fa1e78 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 15 Jun 2018 13:52:33 +0300 Subject: [PATCH 20/45] PlayerWiget: refactor the seekbar connect signal. --- hammond-gtk/src/widgets/player.rs | 34 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index 9dd5c3a..5c2a26a 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -189,7 +189,8 @@ impl PlayerWidget { })); - Self::connect_timers(s); + let slider_update_signal_id = Self::connect_update_seekbar_signal(s); + Self::connect_timers(s, slider_update_signal_id); } fn reveal(&self) { @@ -227,35 +228,32 @@ impl PlayerWidget { // FIXME: Refactor to use gst_player::Player instead of raw pipeline. // FIXME: Refactor the labels to use some kind of Human™ time/values. // Adapted from https://github.com/sdroege/gstreamer-rs/blob/f4d57a66522183d4927b47af422e8f321182111f/tutorials/src/bin/basic-tutorial-5.rs#L131-L164 - fn connect_timers(s: &Rc) { - let slider_update_signal_id = s.timer.scalebar.connect_value_changed( - clone!(s => move |slider| { - let pipeline = &s.player.get_pipeline(); - - let value = slider.get_value() as u64; - if let Err(_) = pipeline.seek_simple( - gst::SeekFlags::FLUSH | gst::SeekFlags::KEY_UNIT, - value * gst::SECOND, - ) { - error!("Seeking to {} failed", value); - } - }), - ); - + fn connect_timers(s: &Rc, update_signal_id: SignalHandlerId) { // Update the PlayerTimes gtk::timeout_add( 250, clone!(s => move || { // TODO: use Player::connect_duration_changed() instead - s.on_duration_changed(&slider_update_signal_id); + s.on_duration_changed(&update_signal_id); // TODO: use Player::connect_position_updated() instead - s.on_position_changed(&slider_update_signal_id); + s.on_position_changed(&update_signal_id); Continue(true) }), ); } + fn connect_update_seekbar_signal(s: &Rc) -> SignalHandlerId { + s.timer + .scalebar + .connect_value_changed(clone!(s => move |slider| { + let player = &s.player; + + let value = slider.get_value() as u64; + player.seek(gst::ClockTime::from_seconds(value as u64)); + })) + } + /// Update the duration `gtk::Label` and the max range of the `gtk::SclaeBar`. fn on_duration_changed(&self, slider_update: &SignalHandlerId) { let pipeline = &self.player.get_pipeline(); From 70914b6c3e23eb9a50429979be75cbcdc882bd9e Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 15 Jun 2018 16:38:50 +0300 Subject: [PATCH 21/45] PlayerWigdet: Refactor the way the duration label is updated. This now connect's directly to gst_player::Player::connect_duration_changed method. The method then sends a cross-thread msg to the Action channel in the main loop that then updates the widget. --- hammond-gtk/src/app.rs | 6 ++- hammond-gtk/src/main.rs | 5 +- hammond-gtk/src/widgets/mod.rs | 3 +- hammond-gtk/src/widgets/player.rs | 83 ++++++++++++++++--------------- 4 files changed, 51 insertions(+), 46 deletions(-) diff --git a/hammond-gtk/src/app.rs b/hammond-gtk/src/app.rs index 6573f98..c0f3abc 100644 --- a/hammond-gtk/src/app.rs +++ b/hammond-gtk/src/app.rs @@ -5,6 +5,7 @@ use gio::{ SimpleAction, SimpleActionExt, }; use glib; +use gst::ClockTime; use gtk; use gtk::prelude::*; use gtk::SettingsExt as GtkSettingsExt; @@ -17,7 +18,8 @@ use settings::{self, WindowGeometry}; use stacks::{Content, PopulatedState}; use utils; use widgets::appnotif::{InAppNotification, UndoState}; -use widgets::{about_dialog, mark_all_notif, remove_show_notif, PlayerWidget}; +use widgets::player::*; +use widgets::{about_dialog, mark_all_notif, remove_show_notif}; use std::rc::Rc; use std::sync::Arc; @@ -54,6 +56,7 @@ pub enum Action { RemoveShow(Arc), ErrorNotification(String), InitEpisode(i32), + PlayerDurationChanged(ClockTime), } #[derive(Debug)] @@ -208,6 +211,7 @@ impl App { notif.show(&overlay); }, Ok(Action::InitEpisode(rowid)) => player.initialize_episode(rowid).unwrap(), + Ok(Action::PlayerDurationChanged(clock)) => player.on_duration_changed(clock), Err(_) => (), } diff --git a/hammond-gtk/src/main.rs b/hammond-gtk/src/main.rs index e5d6d6a..9322f42 100644 --- a/hammond-gtk/src/main.rs +++ b/hammond-gtk/src/main.rs @@ -10,8 +10,8 @@ extern crate gdk; extern crate gdk_pixbuf; extern crate gio; extern crate glib; -extern crate gstreamer; -extern crate gstreamer_player; +extern crate gstreamer as gst; +extern crate gstreamer_player as gst_player; extern crate gtk; #[macro_use] @@ -44,7 +44,6 @@ extern crate url; use log::Level; -use gstreamer as gst; use gtk::prelude::*; // http://gtk-rs.org/tuto/closures diff --git a/hammond-gtk/src/widgets/mod.rs b/hammond-gtk/src/widgets/mod.rs index 820baf3..f852617 100644 --- a/hammond-gtk/src/widgets/mod.rs +++ b/hammond-gtk/src/widgets/mod.rs @@ -3,7 +3,7 @@ pub mod appnotif; mod empty; mod episode; mod home_view; -mod player; +pub mod player; mod show; mod shows_view; @@ -11,7 +11,6 @@ pub use self::aboutdialog::about_dialog; pub use self::empty::EmptyView; pub use self::episode::EpisodeWidget; pub use self::home_view::HomeView; -pub use self::player::PlayerWidget; pub use self::show::ShowWidget; pub use self::show::{mark_all_notif, remove_show_notif}; pub use self::shows_view::ShowsView; diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index 5c2a26a..e989a47 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -1,15 +1,18 @@ +// #![allow(warnings)] + use gio::{File, FileExt}; use glib::SignalHandlerId; +use gst; use gst::prelude::*; -use gstreamer as gst; -use gstreamer::ClockTime; -use gstreamer_player as gst_player; +use gst::ClockTime; +use gst_player; use gtk; use gtk::prelude::*; use crossbeam_channel::Sender; use failure::Error; +// use send_cell::SendCell; use hammond_data::{dbqueries, USER_AGENT}; use hammond_data::{EpisodeWidgetQuery, PodcastCoverQuery}; @@ -19,6 +22,7 @@ use utils::set_image_from_path; use std::path::Path; use std::rc::Rc; +use std::sync::Arc; #[derive(Debug, Clone, Copy)] pub enum SeekDirection { @@ -68,12 +72,13 @@ impl PlayerInfo { } #[derive(Debug, Clone)] -struct PlayerTimes { +pub struct PlayerTimes { container: gtk::Box, progressed: gtk::Label, duration: gtk::Label, separator: gtk::Label, - scalebar: gtk::Scale, + slider: gtk::Scale, + slider_update: Arc, } #[derive(Debug, Clone)] @@ -125,14 +130,16 @@ impl Default for PlayerWidget { let progressed = builder.get_object("progress_time_label").unwrap(); let duration = builder.get_object("total_duration_label").unwrap(); let separator = builder.get_object("separator").unwrap(); - let scalebar: gtk::Scale = builder.get_object("seek").unwrap(); - scalebar.set_range(0.0, 1.0); + let slider: gtk::Scale = builder.get_object("seek").unwrap(); + slider.set_range(0.0, 1.0); + let slider_update = Arc::new(Self::connect_update_slider(&slider, &player)); let timer = PlayerTimes { container: timer_container, progressed, duration, separator, - scalebar, + slider, + slider_update, }; let labels = builder.get_object("info").unwrap(); @@ -189,8 +196,13 @@ impl PlayerWidget { })); - let slider_update_signal_id = Self::connect_update_seekbar_signal(s); - Self::connect_timers(s, slider_update_signal_id); + s.player.connect_duration_changed(clone!(sender => move |_, duration| { + sender.send(Action::PlayerDurationChanged(duration)) + .map_err(|err| error!("Error: {}", err)) + .ok(); + })); + + Self::connect_timers(s); } fn reveal(&self) { @@ -228,62 +240,53 @@ impl PlayerWidget { // FIXME: Refactor to use gst_player::Player instead of raw pipeline. // FIXME: Refactor the labels to use some kind of Human™ time/values. // Adapted from https://github.com/sdroege/gstreamer-rs/blob/f4d57a66522183d4927b47af422e8f321182111f/tutorials/src/bin/basic-tutorial-5.rs#L131-L164 - fn connect_timers(s: &Rc, update_signal_id: SignalHandlerId) { + fn connect_timers(s: &Rc) { // Update the PlayerTimes gtk::timeout_add( 250, clone!(s => move || { - // TODO: use Player::connect_duration_changed() instead - s.on_duration_changed(&update_signal_id); // TODO: use Player::connect_position_updated() instead - s.on_position_changed(&update_signal_id); + s.on_position_changed(); Continue(true) }), ); } - fn connect_update_seekbar_signal(s: &Rc) -> SignalHandlerId { - s.timer - .scalebar - .connect_value_changed(clone!(s => move |slider| { - let player = &s.player; - - let value = slider.get_value() as u64; - player.seek(gst::ClockTime::from_seconds(value as u64)); - })) + fn connect_update_slider(slider: >k::Scale, player: &gst_player::Player) -> SignalHandlerId { + slider.connect_value_changed(clone!(player => move |slider| { + let value = slider.get_value() as u64; + player.seek(ClockTime::from_seconds(value as u64)); + })) } /// Update the duration `gtk::Label` and the max range of the `gtk::SclaeBar`. - fn on_duration_changed(&self, slider_update: &SignalHandlerId) { - let pipeline = &self.player.get_pipeline(); - let slider = &self.timer.scalebar; + // FIXME: Refactor the labels to use some kind of Human™ time/values. + pub fn on_duration_changed(&self, duration: ClockTime) { + let slider = &self.timer.slider; + let seconds = duration.seconds().map(|v| v as f64).unwrap_or(0.0); - if let Some(dur) = pipeline.query_duration::() { - let seconds = dur / gst::SECOND; - let seconds = seconds.map(|v| v as f64).unwrap_or(0.0); + slider.block_signal(&self.timer.slider_update); + slider.set_range(0.0, seconds); + slider.unblock_signal(&self.timer.slider_update); - slider.block_signal(&slider_update); - slider.set_range(0.0, seconds); - slider.unblock_signal(&slider_update); - self.timer - .duration - .set_text(&format!("{:.2}", seconds / 60.0)); - } + self.timer + .duration + .set_text(&format!("{:.2}", seconds / 60.0)); } /// Update the `gtk::SclaeBar` when the pipeline position is changed.. - fn on_position_changed(&self, slider_update: &SignalHandlerId) { + fn on_position_changed(&self) { let pipeline = &self.player.get_pipeline(); - let slider = &self.timer.scalebar; + let slider = &self.timer.slider; if let Some(pos) = pipeline.query_position::() { let seconds = pos / gst::SECOND; let seconds = seconds.map(|v| v as f64).unwrap_or(0.0); - slider.block_signal(&slider_update); + slider.block_signal(&self.timer.slider_update); slider.set_value(seconds); - slider.unblock_signal(&slider_update); + slider.unblock_signal(&self.timer.slider_update); self.timer .progressed From da467b7837e7fb04a5a539764c014419833095e0 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 15 Jun 2018 17:34:05 +0300 Subject: [PATCH 22/45] PlayerWidget::seek handle the case where the offset might be none. --- hammond-gtk/src/widgets/player.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index e989a47..8421ba0 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -321,7 +321,7 @@ impl PlayerExt for PlayerWidget { // Adapted from https://github.com/philn/glide/blob/b52a65d99daeab0b487f79a0e1ccfad0cd433e22/src/player_context.rs#L219-L245 fn seek(&self, offset: ClockTime, direction: SeekDirection) { let position = self.player.get_position(); - if position == ClockTime::none() { + if position.is_none() || offset.is_none() { return; } From 50b480ee2342e6b966dcb1f89eb28f7051a97bbe Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 15 Jun 2018 17:50:26 +0300 Subject: [PATCH 23/45] PlayerWidget: Refactor the position_changed/updated callback. It now uses gst_player::Player::connect_position_updated callback to send, cross threads, the `position` value to the gtk main loop which then updates the widget. --- hammond-gtk/src/app.rs | 2 ++ hammond-gtk/src/widgets/player.rs | 54 +++++++++++-------------------- 2 files changed, 21 insertions(+), 35 deletions(-) diff --git a/hammond-gtk/src/app.rs b/hammond-gtk/src/app.rs index c0f3abc..e112e68 100644 --- a/hammond-gtk/src/app.rs +++ b/hammond-gtk/src/app.rs @@ -57,6 +57,7 @@ pub enum Action { ErrorNotification(String), InitEpisode(i32), PlayerDurationChanged(ClockTime), + PlayerPositionUpdated(ClockTime), } #[derive(Debug)] @@ -212,6 +213,7 @@ impl App { }, Ok(Action::InitEpisode(rowid)) => player.initialize_episode(rowid).unwrap(), Ok(Action::PlayerDurationChanged(clock)) => player.on_duration_changed(clock), + Ok(Action::PlayerPositionUpdated(clock)) => player.on_position_updated(clock), Err(_) => (), } diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index 8421ba0..d3e8402 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -1,15 +1,16 @@ // #![allow(warnings)] -use gio::{File, FileExt}; - -use glib::SignalHandlerId; -use gst; +// use gst; use gst::prelude::*; use gst::ClockTime; use gst_player; + use gtk; use gtk::prelude::*; +use gio::{File, FileExt}; +use glib::SignalHandlerId; + use crossbeam_channel::Sender; use failure::Error; // use send_cell::SendCell; @@ -202,7 +203,11 @@ impl PlayerWidget { .ok(); })); - Self::connect_timers(s); + s.player.connect_position_updated(clone!(sender => move |_, position| { + sender.send(Action::PlayerPositionUpdated(position)) + .map_err(|err| error!("Error: {}", err)) + .ok(); + })); } fn reveal(&self) { @@ -237,22 +242,6 @@ impl PlayerWidget { unimplemented!() } - // FIXME: Refactor to use gst_player::Player instead of raw pipeline. - // FIXME: Refactor the labels to use some kind of Human™ time/values. - // Adapted from https://github.com/sdroege/gstreamer-rs/blob/f4d57a66522183d4927b47af422e8f321182111f/tutorials/src/bin/basic-tutorial-5.rs#L131-L164 - fn connect_timers(s: &Rc) { - // Update the PlayerTimes - gtk::timeout_add( - 250, - clone!(s => move || { - // TODO: use Player::connect_position_updated() instead - s.on_position_changed(); - - Continue(true) - }), - ); - } - fn connect_update_slider(slider: >k::Scale, player: &gst_player::Player) -> SignalHandlerId { slider.connect_value_changed(clone!(player => move |slider| { let value = slider.get_value() as u64; @@ -275,23 +264,18 @@ impl PlayerWidget { .set_text(&format!("{:.2}", seconds / 60.0)); } - /// Update the `gtk::SclaeBar` when the pipeline position is changed.. - fn on_position_changed(&self) { - let pipeline = &self.player.get_pipeline(); + /// Update the `gtk::SclaeBar` when the pipeline position is changed. + pub fn on_position_updated(&self, position: ClockTime) { let slider = &self.timer.slider; + let seconds = position.seconds().map(|v| v as f64).unwrap_or(0.0); - if let Some(pos) = pipeline.query_position::() { - let seconds = pos / gst::SECOND; - let seconds = seconds.map(|v| v as f64).unwrap_or(0.0); + slider.block_signal(&self.timer.slider_update); + slider.set_value(seconds); + slider.unblock_signal(&self.timer.slider_update); - slider.block_signal(&self.timer.slider_update); - slider.set_value(seconds); - slider.unblock_signal(&self.timer.slider_update); - - self.timer - .progressed - .set_text(&format!("{:.2}", seconds / 60.0)); - } + self.timer + .progressed + .set_text(&format!("{:.2}", seconds / 60.0)); } } From 0080399db2fb6e30465ed0aa59947b4b20a99d89 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 15 Jun 2018 18:05:19 +0300 Subject: [PATCH 24/45] PlayerWidget: Move on_duration_change and on_postion_updated methods. Previously they depended on the player/pipeline to get the ClockTime values, and only `PlayerWidget` had access to the `gst_player::Player` object. Now that it uses the gst_player methods instead of the raw pipeline methods to get the ClockTime values it no longer needs access to the whole PlayerWidget object. --- hammond-gtk/src/app.rs | 4 +-- hammond-gtk/src/widgets/player.rs | 56 ++++++++++++++----------------- 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/hammond-gtk/src/app.rs b/hammond-gtk/src/app.rs index e112e68..a47382a 100644 --- a/hammond-gtk/src/app.rs +++ b/hammond-gtk/src/app.rs @@ -212,8 +212,8 @@ impl App { notif.show(&overlay); }, Ok(Action::InitEpisode(rowid)) => player.initialize_episode(rowid).unwrap(), - Ok(Action::PlayerDurationChanged(clock)) => player.on_duration_changed(clock), - Ok(Action::PlayerPositionUpdated(clock)) => player.on_position_updated(clock), + Ok(Action::PlayerDurationChanged(clock)) => player.timer.on_duration_changed(clock), + Ok(Action::PlayerPositionUpdated(clock)) => player.timer.on_position_updated(clock), Err(_) => (), } diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index d3e8402..6b938be 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -82,6 +82,31 @@ pub struct PlayerTimes { slider_update: Arc, } +impl PlayerTimes { + /// Update the duration `gtk::Label` and the max range of the `gtk::SclaeBar`. + // FIXME: Refactor the labels to use some kind of Human™ time/values. + pub fn on_duration_changed(&self, duration: ClockTime) { + let seconds = duration.seconds().map(|v| v as f64).unwrap_or(0.0); + + self.slider.block_signal(&self.slider_update); + self.slider.set_range(0.0, seconds); + self.slider.unblock_signal(&self.slider_update); + + self.duration.set_text(&format!("{:.2}", seconds / 60.0)); + } + + /// Update the `gtk::SclaeBar` when the pipeline position is changed. + pub fn on_position_updated(&self, position: ClockTime) { + let seconds = position.seconds().map(|v| v as f64).unwrap_or(0.0); + + self.slider.block_signal(&self.slider_update); + self.slider.set_value(seconds); + self.slider.unblock_signal(&self.slider_update); + + self.progressed.set_text(&format!("{:.2}", seconds / 60.0)); + } +} + #[derive(Debug, Clone)] struct PlayerControls { container: gtk::Box, @@ -96,7 +121,7 @@ pub struct PlayerWidget { pub action_bar: gtk::ActionBar, player: gst_player::Player, controls: PlayerControls, - timer: PlayerTimes, + pub timer: PlayerTimes, info: PlayerInfo, } @@ -248,35 +273,6 @@ impl PlayerWidget { player.seek(ClockTime::from_seconds(value as u64)); })) } - - /// Update the duration `gtk::Label` and the max range of the `gtk::SclaeBar`. - // FIXME: Refactor the labels to use some kind of Human™ time/values. - pub fn on_duration_changed(&self, duration: ClockTime) { - let slider = &self.timer.slider; - let seconds = duration.seconds().map(|v| v as f64).unwrap_or(0.0); - - slider.block_signal(&self.timer.slider_update); - slider.set_range(0.0, seconds); - slider.unblock_signal(&self.timer.slider_update); - - self.timer - .duration - .set_text(&format!("{:.2}", seconds / 60.0)); - } - - /// Update the `gtk::SclaeBar` when the pipeline position is changed. - pub fn on_position_updated(&self, position: ClockTime) { - let slider = &self.timer.slider; - let seconds = position.seconds().map(|v| v as f64).unwrap_or(0.0); - - slider.block_signal(&self.timer.slider_update); - slider.set_value(seconds); - slider.unblock_signal(&self.timer.slider_update); - - self.timer - .progressed - .set_text(&format!("{:.2}", seconds / 60.0)); - } } impl PlayerExt for PlayerWidget { From a6a34d8246a1ff48f683f4191c0f25dfffe7b121 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 15 Jun 2018 18:19:17 +0300 Subject: [PATCH 25/45] PlayerWidget: Group the button connect_clicked methods. --- hammond-gtk/src/widgets/player.rs | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index 6b938be..dd53fc1 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -198,17 +198,7 @@ impl PlayerWidget { #[cfg_attr(rustfmt, rustfmt_skip)] fn init(s: &Rc, sender: &Sender) { - // Connect the play button to the gst Player. - s.controls.play.connect_clicked(clone!(s => move |_| s.play())); - - // Connect the pause button to the gst Player. - s.controls.pause.connect_clicked(clone!(s => move |_| s.pause())); - - // Connect the rewind button to the gst Player. - s.controls.rewind.connect_clicked(clone!(s => move |_| s.rewind())); - - // Connect the fast-forward button to the gst Player. - s.controls.forward.connect_clicked(clone!(s => move |_| s.fast_forward())); + Self::connect_buttons(s); // Log gst warnings. s.player.connect_warning(move |_, warn| warn!("gst warning: {}", warn)); @@ -235,6 +225,22 @@ impl PlayerWidget { })); } + #[cfg_attr(rustfmt, rustfmt_skip)] + /// Connect the `PlayerControls` buttons to the `PlayerExt` methods. + fn connect_buttons(s: &Rc) { + // Connect the play button to the gst Player. + s.controls.play.connect_clicked(clone!(s => move |_| s.play())); + + // Connect the pause button to the gst Player. + s.controls.pause.connect_clicked(clone!(s => move |_| s.pause())); + + // Connect the rewind button to the gst Player. + s.controls.rewind.connect_clicked(clone!(s => move |_| s.rewind())); + + // Connect the fast-forward button to the gst Player. + s.controls.forward.connect_clicked(clone!(s => move |_| s.fast_forward())); + } + fn reveal(&self) { self.action_bar.show(); } From b58d28c72393526a19f01ccab6437758ece9802c Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Sat, 16 Jun 2018 12:35:51 +0300 Subject: [PATCH 26/45] PlayerTimes: create wrapper struct of gst::ClockTime. Now on_duration_changed requires a `Duration` type and on_position_updated requires a `Position` type as oppose to both accepting `ClockTime` as their argument. --- hammond-gtk/src/app.rs | 13 ++++++------ hammond-gtk/src/widgets/player.rs | 33 +++++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/hammond-gtk/src/app.rs b/hammond-gtk/src/app.rs index a47382a..3b26cce 100644 --- a/hammond-gtk/src/app.rs +++ b/hammond-gtk/src/app.rs @@ -5,7 +5,6 @@ use gio::{ SimpleAction, SimpleActionExt, }; use glib; -use gst::ClockTime; use gtk; use gtk::prelude::*; use gtk::SettingsExt as GtkSettingsExt; @@ -18,7 +17,7 @@ use settings::{self, WindowGeometry}; use stacks::{Content, PopulatedState}; use utils; use widgets::appnotif::{InAppNotification, UndoState}; -use widgets::player::*; +use widgets::player; use widgets::{about_dialog, mark_all_notif, remove_show_notif}; use std::rc::Rc; @@ -56,8 +55,8 @@ pub enum Action { RemoveShow(Arc), ErrorNotification(String), InitEpisode(i32), - PlayerDurationChanged(ClockTime), - PlayerPositionUpdated(ClockTime), + PlayerDurationChanged(player::Duration), + PlayerPositionUpdated(player::Position), } #[derive(Debug)] @@ -123,7 +122,7 @@ impl App { // Add the overlay to the main Box wrap.add(&overlay); - let player = PlayerWidget::new(&sender); + let player = player::PlayerWidget::new(&sender); // Add the player to the main Box wrap.add(&player.action_bar); // player.reveal(); @@ -212,8 +211,8 @@ impl App { notif.show(&overlay); }, Ok(Action::InitEpisode(rowid)) => player.initialize_episode(rowid).unwrap(), - Ok(Action::PlayerDurationChanged(clock)) => player.timer.on_duration_changed(clock), - Ok(Action::PlayerPositionUpdated(clock)) => player.timer.on_position_updated(clock), + Ok(Action::PlayerDurationChanged(dur)) => player.timer.on_duration_changed(dur), + Ok(Action::PlayerPositionUpdated(pos)) => player.timer.on_position_updated(pos), Err(_) => (), } diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index dd53fc1..c4dfd13 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -21,6 +21,7 @@ use hammond_data::{EpisodeWidgetQuery, PodcastCoverQuery}; use app::Action; use utils::set_image_from_path; +use std::ops::Deref; use std::path::Path; use std::rc::Rc; use std::sync::Arc; @@ -82,10 +83,30 @@ pub struct PlayerTimes { slider_update: Arc, } +#[derive(Debug, Clone, Copy)] +pub struct Duration(ClockTime); + +impl Deref for Duration { + type Target = ClockTime; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Position(ClockTime); + +impl Deref for Position { + type Target = ClockTime; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + impl PlayerTimes { /// Update the duration `gtk::Label` and the max range of the `gtk::SclaeBar`. // FIXME: Refactor the labels to use some kind of Human™ time/values. - pub fn on_duration_changed(&self, duration: ClockTime) { + pub fn on_duration_changed(&self, duration: Duration) { let seconds = duration.seconds().map(|v| v as f64).unwrap_or(0.0); self.slider.block_signal(&self.slider_update); @@ -96,7 +117,7 @@ impl PlayerTimes { } /// Update the `gtk::SclaeBar` when the pipeline position is changed. - pub fn on_position_updated(&self, position: ClockTime) { + pub fn on_position_updated(&self, position: Position) { let seconds = position.seconds().map(|v| v as f64).unwrap_or(0.0); self.slider.block_signal(&self.slider_update); @@ -212,14 +233,14 @@ impl PlayerWidget { })); - s.player.connect_duration_changed(clone!(sender => move |_, duration| { - sender.send(Action::PlayerDurationChanged(duration)) + s.player.connect_duration_changed(clone!(sender => move |_, clock| { + sender.send(Action::PlayerDurationChanged(Duration(clock))) .map_err(|err| error!("Error: {}", err)) .ok(); })); - s.player.connect_position_updated(clone!(sender => move |_, position| { - sender.send(Action::PlayerPositionUpdated(position)) + s.player.connect_position_updated(clone!(sender => move |_, clock| { + sender.send(Action::PlayerPositionUpdated(Position(clock))) .map_err(|err| error!("Error: {}", err)) .ok(); })); From c42822669b46faffe5f581ca4580d82cb44092be Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Sat, 16 Jun 2018 12:40:31 +0300 Subject: [PATCH 27/45] PlayerTimes: Replace unnecessary `Arc` with `Rc`. --- hammond-gtk/src/widgets/player.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index c4dfd13..082b2c5 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -24,7 +24,6 @@ use utils::set_image_from_path; use std::ops::Deref; use std::path::Path; use std::rc::Rc; -use std::sync::Arc; #[derive(Debug, Clone, Copy)] pub enum SeekDirection { @@ -80,7 +79,7 @@ pub struct PlayerTimes { duration: gtk::Label, separator: gtk::Label, slider: gtk::Scale, - slider_update: Arc, + slider_update: Rc, } #[derive(Debug, Clone, Copy)] @@ -179,7 +178,7 @@ impl Default for PlayerWidget { let separator = builder.get_object("separator").unwrap(); let slider: gtk::Scale = builder.get_object("seek").unwrap(); slider.set_range(0.0, 1.0); - let slider_update = Arc::new(Self::connect_update_slider(&slider, &player)); + let slider_update = Rc::new(Self::connect_update_slider(&slider, &player)); let timer = PlayerTimes { container: timer_container, progressed, From 55b1504aab9e6c64bd6fddbcd19f718dd6f357f6 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Sat, 16 Jun 2018 14:33:21 +0300 Subject: [PATCH 28/45] PlayerTimes: Display human-friendly values. --- hammond-gtk/src/widgets/player.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index 082b2c5..326908b 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -11,6 +11,7 @@ use gtk::prelude::*; use gio::{File, FileExt}; use glib::SignalHandlerId; +use chrono::NaiveTime; use crossbeam_channel::Sender; use failure::Error; // use send_cell::SendCell; @@ -104,7 +105,6 @@ impl Deref for Position { impl PlayerTimes { /// Update the duration `gtk::Label` and the max range of the `gtk::SclaeBar`. - // FIXME: Refactor the labels to use some kind of Human™ time/values. pub fn on_duration_changed(&self, duration: Duration) { let seconds = duration.seconds().map(|v| v as f64).unwrap_or(0.0); @@ -112,7 +112,7 @@ impl PlayerTimes { self.slider.set_range(0.0, seconds); self.slider.unblock_signal(&self.slider_update); - self.duration.set_text(&format!("{:.2}", seconds / 60.0)); + self.duration.set_text(&format_duration(seconds as u32)); } /// Update the `gtk::SclaeBar` when the pipeline position is changed. @@ -123,7 +123,17 @@ impl PlayerTimes { self.slider.set_value(seconds); self.slider.unblock_signal(&self.slider_update); - self.progressed.set_text(&format!("{:.2}", seconds / 60.0)); + self.progressed.set_text(&format_duration(seconds as u32)); + } +} + +fn format_duration(seconds: u32) -> String { + let time = NaiveTime::from_num_seconds_from_midnight(seconds, 0); + + if seconds >= 3600 { + time.format("%T").to_string() + } else { + time.format("%M:%S").to_string() } } From a596b62a5f352473d12f38c7732b9000a984389f Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Sat, 16 Jun 2018 15:59:22 +0300 Subject: [PATCH 29/45] PlayerWidget: Tweak gst_player config. --- hammond-gtk/src/widgets/player.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index 326908b..980cf32 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -162,7 +162,6 @@ impl Default for PlayerWidget { let mut config = player.get_config(); config.set_user_agent(USER_AGENT); config.set_position_update_interval(250); - config.set_seek_accurate(true); player.set_config(config).unwrap(); let builder = gtk::Builder::new_from_resource("/org/gnome/Hammond/gtk/player_toolbar.ui"); From 2fcb8d915d2c669002d91389618fbc72fafe996b Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Sat, 16 Jun 2018 16:29:40 +0300 Subject: [PATCH 30/45] PlayerWidget: Remove an .expect() occurrence. --- hammond-gtk/src/widgets/player.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index 980cf32..103ef39 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -286,20 +286,21 @@ impl PlayerWidget { // path is an absolute fs path ex. "foo/bar/baz". // Convert it so it will have a "file:///" // FIXME: convert it properly - let uri = File::new_for_path(path).get_uri().expect("Bad file path"); + if let Some(uri) = File::new_for_path(path).get_uri() { + // FIXME: Maybe should also reset/flush the pipeline and then add the file? - // FIXME: Maybe should also reset/flush the pipeline and then add the file? - - // play the file - self.player.set_uri(&uri); - self.play(); - return Ok(()); + // play the file + self.player.set_uri(&uri); + self.play(); + return Ok(()); + } } // TODO: log an error } - // Stream stuff - unimplemented!() + // FIXME: Stream stuff + // unimplemented!() + Ok(()) } fn connect_update_slider(slider: >k::Scale, player: &gst_player::Player) -> SignalHandlerId { From 745afb32a3ee6142a895216ad2c0df7cbf44dc84 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Sat, 16 Jun 2018 19:50:23 +0300 Subject: [PATCH 31/45] PlayerWidget: Rewind on pause. --- hammond-gtk/src/widgets/player.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index 103ef39..8c1d338 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -332,6 +332,7 @@ impl PlayerExt for PlayerWidget { self.controls.play.show(); self.player.pause(); + self.seek(ClockTime::from_seconds(5), SeekDirection::Backwards); } // Adapted from https://github.com/philn/glide/blob/b52a65d99daeab0b487f79a0e1ccfad0cd433e22/src/player_context.rs#L219-L245 From a83270699f556d511038e2310854f4105e2fce71 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Mon, 18 Jun 2018 18:05:53 +0300 Subject: [PATCH 32/45] PlayerWidget: refactor seek method. --- hammond-gtk/src/widgets/player.rs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index 8c1d338..596eb35 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -342,22 +342,13 @@ impl PlayerExt for PlayerWidget { return; } + let duration = self.player.get_duration(); let destination = match direction { - SeekDirection::Backwards => { - if position >= offset { - Some(position - offset) - } else { - None - } - } - SeekDirection::Forward => { - let duration = self.player.get_duration(); - if duration != ClockTime::none() && position + offset <= duration { - Some(position + offset) - } else { - None - } + SeekDirection::Backwards if position >= offset => Some(position - offset), + SeekDirection::Forward if !duration.is_none() && position + offset <= duration => { + Some(position + offset) } + _ => None, }; destination.map(|d| self.player.seek(d)); From 3e2ab8e7ee713ed12f51ac1f435559d585f150c5 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Mon, 18 Jun 2018 19:09:12 +0300 Subject: [PATCH 33/45] PlayerExt: Add a stop method. Also connect on the gst_player::Player::connect_on_stream_end method. The main reason for this is to be able to reset the slider bar to 0 upon a stream ends. --- hammond-gtk/src/app.rs | 4 ++++ hammond-gtk/src/widgets/player.rs | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/hammond-gtk/src/app.rs b/hammond-gtk/src/app.rs index 3b26cce..2c11a62 100644 --- a/hammond-gtk/src/app.rs +++ b/hammond-gtk/src/app.rs @@ -5,6 +5,7 @@ use gio::{ SimpleAction, SimpleActionExt, }; use glib; +use gst_player; use gtk; use gtk::prelude::*; use gtk::SettingsExt as GtkSettingsExt; @@ -18,6 +19,7 @@ use stacks::{Content, PopulatedState}; use utils; use widgets::appnotif::{InAppNotification, UndoState}; use widgets::player; +use widgets::player::PlayerExt; use widgets::{about_dialog, mark_all_notif, remove_show_notif}; use std::rc::Rc; @@ -57,6 +59,7 @@ pub enum Action { InitEpisode(i32), PlayerDurationChanged(player::Duration), PlayerPositionUpdated(player::Position), + PlayerEndofStream(gst_player::Player), } #[derive(Debug)] @@ -213,6 +216,7 @@ impl App { Ok(Action::InitEpisode(rowid)) => player.initialize_episode(rowid).unwrap(), Ok(Action::PlayerDurationChanged(dur)) => player.timer.on_duration_changed(dur), Ok(Action::PlayerPositionUpdated(pos)) => player.timer.on_position_updated(pos), + Ok(Action::PlayerEndofStream(_)) => player.stop(), Err(_) => (), } diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index 596eb35..25cb21f 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -35,6 +35,7 @@ pub enum SeekDirection { pub trait PlayerExt { fn play(&self); fn pause(&self); + fn stop(&self); fn seek(&self, offset: ClockTime, direction: SeekDirection); fn fast_forward(&self); fn rewind(&self); @@ -252,6 +253,12 @@ impl PlayerWidget { .map_err(|err| error!("Error: {}", err)) .ok(); })); + + s.player.connect_end_of_stream(clone!(sender => move |player| { + sender.send(Action::PlayerEndofStream(player.clone())) + .map_err(|err| error!("Error: {}", err)) + .ok(); + })); } #[cfg_attr(rustfmt, rustfmt_skip)] @@ -335,6 +342,16 @@ impl PlayerExt for PlayerWidget { self.seek(ClockTime::from_seconds(5), SeekDirection::Backwards); } + #[cfg_attr(rustfmt, rustfmt_skip)] + fn stop(&self) { + self.controls.pause.hide(); + self.controls.play.show(); + + self.player.stop(); + + self.timer.on_position_updated(Position(ClockTime::from_seconds(0))); + } + // Adapted from https://github.com/philn/glide/blob/b52a65d99daeab0b487f79a0e1ccfad0cd433e22/src/player_context.rs#L219-L245 fn seek(&self, offset: ClockTime, direction: SeekDirection) { let position = self.player.get_position(); From a93d5246d22256d41a112ed10f61919908f046e1 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Mon, 18 Jun 2018 21:39:37 +0300 Subject: [PATCH 34/45] PlayerInfo: Swap bold properties of the labels. --- hammond-gtk/resources/gtk/style.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hammond-gtk/resources/gtk/style.css b/hammond-gtk/resources/gtk/style.css index b4d2284..3af698c 100644 --- a/hammond-gtk/resources/gtk/style.css +++ b/hammond-gtk/resources/gtk/style.css @@ -10,11 +10,11 @@ list, border { border-radius: 4px; } -.player-show-label { +.player-episode-label { font-weight: bold; font-size: smaller; } -.player-episode-label { +.player-show-label { font-size: smaller; } \ No newline at end of file From 590f815dc0cba4fa5bc11c3f9188c9bc3fe05ab9 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Mon, 18 Jun 2018 22:49:08 +0300 Subject: [PATCH 35/45] PlayerInfo: Limit label widths and add tooltips. --- hammond-gtk/resources/gtk/player_toolbar.ui | 10 +++++++++- hammond-gtk/src/widgets/player.rs | 6 ++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/hammond-gtk/resources/gtk/player_toolbar.ui b/hammond-gtk/resources/gtk/player_toolbar.ui index 5e246a6..fe389c9 100644 --- a/hammond-gtk/resources/gtk/player_toolbar.ui +++ b/hammond-gtk/resources/gtk/player_toolbar.ui @@ -34,7 +34,7 @@ False True - end + center True @@ -140,7 +140,11 @@ True False + start Show Title + True + end + 20 @@ -155,7 +159,11 @@ True False + start Episode Title + True + end + 20 diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index 25cb21f..8a75d7a 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -60,11 +60,13 @@ impl PlayerInfo { } fn set_episode_title(&self, episode: &EpisodeWidgetQuery) { - self.episode.set_text(&episode.title()); + self.episode.set_text(episode.title()); + self.episode.set_tooltip_text(episode.title()); } fn set_show_title(&self, show: &PodcastCoverQuery) { - self.show.set_text(&show.title()); + self.show.set_text(show.title()); + self.show.set_tooltip_text(show.title()); } fn set_cover_image(&self, show: &PodcastCoverQuery) { From 474cb49d2c8707c77d82487ca8c2d5cd7e2014ce Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Tue, 19 Jun 2018 00:05:29 +0300 Subject: [PATCH 36/45] PlayerInfo: Increase the size of the cover. --- hammond-gtk/resources/gtk/player_toolbar.ui | 4 +++- hammond-gtk/src/widgets/player.rs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/hammond-gtk/resources/gtk/player_toolbar.ui b/hammond-gtk/resources/gtk/player_toolbar.ui index fe389c9..bea3fc4 100644 --- a/hammond-gtk/resources/gtk/player_toolbar.ui +++ b/hammond-gtk/resources/gtk/player_toolbar.ui @@ -39,6 +39,7 @@ True False + center 42 @@ -119,7 +120,8 @@ True False - 24 + center + 34 image-x-generic-symbolic diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index 8a75d7a..9150d89 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -70,7 +70,7 @@ impl PlayerInfo { } fn set_cover_image(&self, show: &PodcastCoverQuery) { - set_image_from_path(&self.cover, show.id(), 24) + set_image_from_path(&self.cover, show.id(), 34) .map_err(|err| error!("Player Cover: {}", err)) .ok(); } From ee8cbbf7ef89605c6df115ec1987155d1544d184 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Tue, 19 Jun 2018 16:34:33 +0300 Subject: [PATCH 37/45] PlayerWidget: Delete commented out stuff. --- hammond-gtk/src/widgets/player.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index 9150d89..5368167 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -1,6 +1,3 @@ -// #![allow(warnings)] - -// use gst; use gst::prelude::*; use gst::ClockTime; use gst_player; @@ -14,7 +11,6 @@ use glib::SignalHandlerId; use chrono::NaiveTime; use crossbeam_channel::Sender; use failure::Error; -// use send_cell::SendCell; use hammond_data::{dbqueries, USER_AGENT}; use hammond_data::{EpisodeWidgetQuery, PodcastCoverQuery}; @@ -296,8 +292,6 @@ impl PlayerWidget { // Convert it so it will have a "file:///" // FIXME: convert it properly if let Some(uri) = File::new_for_path(path).get_uri() { - // FIXME: Maybe should also reset/flush the pipeline and then add the file? - // play the file self.player.set_uri(&uri); self.play(); @@ -322,9 +316,6 @@ impl PlayerWidget { impl PlayerExt for PlayerWidget { fn play(&self) { - // assert the state is either ready or paused - // TODO: assert!() - self.reveal(); self.controls.pause.show(); @@ -334,9 +325,6 @@ impl PlayerExt for PlayerWidget { } fn pause(&self) { - // assert the state is paused - // TODO: assert!() - self.controls.pause.hide(); self.controls.play.show(); From 593d66ea5492dcc026d7452a266ca9d261d3aaa8 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Wed, 20 Jun 2018 12:36:56 +0300 Subject: [PATCH 38/45] EpisodeWidget: Mark an episode as played when the play button is hit. Ideally episodes would be marked as played only when they have passed a cerain point in their duration, but till thats ready we should keep marking them. --- hammond-gtk/src/widgets/episode.rs | 31 ++++++++++-------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/hammond-gtk/src/widgets/episode.rs b/hammond-gtk/src/widgets/episode.rs index 1af0531..aab0310 100644 --- a/hammond-gtk/src/widgets/episode.rs +++ b/hammond-gtk/src/widgets/episode.rs @@ -442,34 +442,23 @@ fn on_download_clicked(ep: &EpisodeWidgetQuery, sender: &Sender) -> Resu } fn on_play_bttn_clicked( - _widget: &Rc, + widget: &Rc, episode: &mut EpisodeWidgetQuery, sender: &Sender, ) -> Result<(), Error> { + // Mark played + episode.set_played_now()?; + // Grey out the title + widget.info.set_title(&episode); + + // Play the episode + sender.send(Action::InitEpisode(episode.rowid()))?; + // Refresh background views to match the normal/greyout title state sender - .send(Action::InitEpisode(episode.rowid())) + .send(Action::RefreshEpisodesViewBGR) .map_err(From::from) - - // widget.info.set_title(&episode); - // sender - // .send(Action::RefreshEpisodesViewBGR) - // .map_err(From::from) } -// fn open_uri(rowid: i32) -> Result<(), Error> { -// let uri = dbqueries::get_episode_local_uri_from_id(rowid)? -// .ok_or_else(|| format_err!("Expected Some found None."))?; - -// if Path::new(&uri).exists() { -// info!("Opening {}", uri); -// open::that(&uri)?; -// } else { -// bail!("File \"{}\" does not exist.", uri); -// } - -// Ok(()) -// } - // Setup a callback that will update the progress bar. #[inline] #[cfg_attr(feature = "cargo-clippy", allow(if_same_then_else))] From ff2f43766e39a9e1a692ca8eab5c6f93d4072a49 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Wed, 20 Jun 2018 15:27:52 +0300 Subject: [PATCH 39/45] PlayerWidget: Add a widget to change the playback speed of the stream. Only 3 options are offered currently since the design of the feature is still in progress and this is only a throw a away prototype. --- hammond-gtk/resources/gtk/player_toolbar.ui | 113 ++++++++++++++++++++ hammond-gtk/src/widgets/player.rs | 51 ++++++++- 2 files changed, 160 insertions(+), 4 deletions(-) diff --git a/hammond-gtk/resources/gtk/player_toolbar.ui b/hammond-gtk/resources/gtk/player_toolbar.ui index bea3fc4..d766706 100644 --- a/hammond-gtk/resources/gtk/player_toolbar.ui +++ b/hammond-gtk/resources/gtk/player_toolbar.ui @@ -250,9 +250,122 @@ + + 3 + + + + + True + True + True + center + center + 6 + up + rate_popover + + + True + False + 6 + + + True + False + 1.00x + + + False + True + 0 + + + + + True + False + go-down-symbolic + 1 + + + False + True + 1 + + + + + 4 + + False + rate_button + + + True + False + vertical + 3 + start + + + 1.50x + True + True + False + center + center + True + True + normal_rate + + + True + True + 0 + + + + + 1.25x + True + True + False + center + center + True + True + normal_rate + + + True + True + 1 + + + + + 1.00x + True + True + False + center + center + True + True + + + True + True + 2 + + + + + diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index 5368167..eaa28d2 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -35,8 +35,7 @@ pub trait PlayerExt { fn seek(&self, offset: ClockTime, direction: SeekDirection); fn fast_forward(&self); fn rewind(&self); - // TODO: change playback rate - // fn set_playback_rate(&self); + fn set_playback_rate(&self, f64); } #[derive(Debug, Clone)] @@ -136,6 +135,16 @@ fn format_duration(seconds: u32) -> String { } } +#[derive(Debug, Clone)] +struct PlayerRate { + radio150: gtk::RadioButton, + radio125: gtk::RadioButton, + radio_normal: gtk::RadioButton, + popover: gtk::Popover, + btn: gtk::MenuButton, + label: gtk::Label, +} + #[derive(Debug, Clone)] struct PlayerControls { container: gtk::Box, @@ -152,6 +161,7 @@ pub struct PlayerWidget { controls: PlayerControls, pub timer: PlayerTimes, info: PlayerInfo, + rate: PlayerRate, } impl Default for PlayerWidget { @@ -207,12 +217,28 @@ impl Default for PlayerWidget { cover, }; + let radio150 = builder.get_object("rate_1_50").unwrap(); + let radio125 = builder.get_object("rate_1_25").unwrap(); + let radio_normal = builder.get_object("normal_rate").unwrap(); + let popover = builder.get_object("rate_popover").unwrap(); + let btn = builder.get_object("rate_button").unwrap(); + let label = builder.get_object("rate_label").unwrap(); + let rate = PlayerRate { + radio150, + radio125, + radio_normal, + popover, + label, + btn, + }; + PlayerWidget { player, action_bar, controls, timer, info, + rate, } } } @@ -226,7 +252,8 @@ impl PlayerWidget { #[cfg_attr(rustfmt, rustfmt_skip)] fn init(s: &Rc, sender: &Sender) { - Self::connect_buttons(s); + Self::connect_control_buttons(s); + Self::connect_rate_buttons(s); // Log gst warnings. s.player.connect_warning(move |_, warn| warn!("gst warning: {}", warn)); @@ -261,7 +288,7 @@ impl PlayerWidget { #[cfg_attr(rustfmt, rustfmt_skip)] /// Connect the `PlayerControls` buttons to the `PlayerExt` methods. - fn connect_buttons(s: &Rc) { + fn connect_control_buttons(s: &Rc) { // Connect the play button to the gst Player. s.controls.play.connect_clicked(clone!(s => move |_| s.play())); @@ -275,6 +302,18 @@ impl PlayerWidget { s.controls.forward.connect_clicked(clone!(s => move |_| s.fast_forward())); } + #[cfg_attr(rustfmt, rustfmt_skip)] + fn connect_rate_buttons(s: &Rc) { + s.rate.radio_normal.connect_toggled(clone!(s => move |_| s.on_rate_changed(1.00))); + s.rate.radio125.connect_toggled(clone!(s => move |_| s.on_rate_changed(1.25))); + s.rate.radio150.connect_toggled(clone!(s => move |_| s.on_rate_changed(1.50))); + } + + fn on_rate_changed(&self, rate: f64) { + self.set_playback_rate(rate); + self.rate.label.set_text(&format!("{:.2}x", rate)); + } + fn reveal(&self) { self.action_bar.show(); } @@ -370,4 +409,8 @@ impl PlayerExt for PlayerWidget { fn fast_forward(&self) { self.seek(ClockTime::from_seconds(10), SeekDirection::Forward) } + + fn set_playback_rate(&self, rate: f64) { + self.player.set_rate(rate); + } } From 38eb14b013a5cde560ce3d2e6fddda2f16a94c27 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Wed, 20 Jun 2018 15:46:46 +0300 Subject: [PATCH 40/45] Delete commented out code. --- hammond-gtk/src/app.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/hammond-gtk/src/app.rs b/hammond-gtk/src/app.rs index 2c11a62..884f400 100644 --- a/hammond-gtk/src/app.rs +++ b/hammond-gtk/src/app.rs @@ -128,7 +128,6 @@ impl App { let player = player::PlayerWidget::new(&sender); // Add the player to the main Box wrap.add(&player.action_bar); - // player.reveal(); window.add(&wrap); @@ -139,23 +138,6 @@ impl App { window.show_all(); window.activate(); - // player.connect_error(clone!(sender => move |_,err| { - // Not the most user friendly... - // sender.send(Action::ErrorNotification(format!("Playback: {}", err))).ok(); - // })); - - // player.connect_state_changed(clone!(sender => move |_,state| { - // sender.send(Action::PlayerStateChanged(state)).ok(); - // })); - - // player.connect_media_info_updated(clone!(sender => move |_,info| { - // sender.send(Action::PlayerMediaChanged(info.get_title(), info.get_duration())).ok(); - // })); - - // player.connect_property_position_notify(clone!(sender => move |p| { - // sender.send(Action::PlayerPositionChanged(p.get_position())).ok(); - // })); - gtk::timeout_add(50, clone!(sender, receiver => move || { // Uses receiver, content, header, sender, overlay, playback match receiver.try_recv() { From 2d879b960483e2342cc0a46056985cc89dbd66b1 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Wed, 20 Jun 2018 16:57:12 +0300 Subject: [PATCH 41/45] PlayerRate: Change the container widget to GtkBox and add padding. --- hammond-gtk/resources/gtk/player_toolbar.ui | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/hammond-gtk/resources/gtk/player_toolbar.ui b/hammond-gtk/resources/gtk/player_toolbar.ui index d766706..653f976 100644 --- a/hammond-gtk/resources/gtk/player_toolbar.ui +++ b/hammond-gtk/resources/gtk/player_toolbar.ui @@ -306,12 +306,15 @@ False rate_button - + True False + 6 + 6 + 6 + 6 vertical 3 - start 1.50x @@ -320,7 +323,6 @@ False center center - True True normal_rate @@ -338,7 +340,6 @@ False center center - True True normal_rate From 79b425326b5c8cb75c8a34f9cd25cdef09813108 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Wed, 20 Jun 2018 17:01:54 +0300 Subject: [PATCH 42/45] Update Changelog. --- CHANGELOG.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26e8735..37e5404 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ### Added: -- Keyboard Shortcuts and a Shortcuts dialog were implemented. (ZanderBrown) [!33](https://gitlab.gnome.org/World/hammond/merge_requests/33) +- Keyboard Shortcuts and a Shortcuts dialog were implemented. (ZanderBrown) +[!33](https://gitlab.gnome.org/World/hammond/merge_requests/33) ### Changed: -- The `FileChooser` of the OPML import was changed to use the `FileChooserNative` widget/API. (ZanderBrown) [!33](https://gitlab.gnome.org/World/hammond/merge_requests/33) -- The `EpisdeWidget` was refactored. [!38](https://gitlab.gnome.org/World/hammond/merge_requests/38) +- The `FileChooser` of the OPML import was changed to use the `FileChooserNative` widget/API. (ZanderBrown) +[!33](https://gitlab.gnome.org/World/hammond/merge_requests/33) +- The `EpisdeWidget` was refactored. +[!38](https://gitlab.gnome.org/World/hammond/merge_requests/38) - `EpisdeWidget`'s progressbar was changed to be non-blocking and should feel way more responsive now. 9b0ac5b83dadecdff51cd398293afdf0d5276012 +- An embeded audio player was implemented! +[!40](https://gitlab.gnome.org/World/hammond/merge_requests/40) ### Fixed: - Fixed a bug whre the about dialog would be unclosable. (ZanderBrown) [!37](https://gitlab.gnome.org/World/hammond/merge_requests/37) From 0686fca3b06bb1cd688c5d65fd873338f2ef6a04 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Thu, 21 Jun 2018 18:48:46 +0300 Subject: [PATCH 43/45] h-gtk: Increase the polling rate of the main thread channel. When dragging the PlayerWidget.timer.slider widget PlayerDurationChanged messaged pile up in the channel and get out of sync with the slider.connect_value_changed() signal. --- hammond-gtk/src/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hammond-gtk/src/app.rs b/hammond-gtk/src/app.rs index 884f400..a2317f9 100644 --- a/hammond-gtk/src/app.rs +++ b/hammond-gtk/src/app.rs @@ -138,7 +138,7 @@ impl App { window.show_all(); window.activate(); - gtk::timeout_add(50, clone!(sender, receiver => move || { + gtk::timeout_add(15, clone!(sender, receiver => move || { // Uses receiver, content, header, sender, overlay, playback match receiver.try_recv() { Ok(Action::RefreshAllViews) => content.update(), From eeef0d13ff6c4619affc1da80cdeab989150cd89 Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Fri, 22 Jun 2018 16:03:00 +0300 Subject: [PATCH 44/45] PlayerWidget: Use the Gtk Main Context for the gst_player as well. There is no longer a need for sending stuff to the main-thread `Action` channel anymore thanks to this. --- hammond-gtk/src/app.rs | 10 +--- hammond-gtk/src/widgets/player.rs | 83 +++++++++++++++++-------------- 2 files changed, 46 insertions(+), 47 deletions(-) diff --git a/hammond-gtk/src/app.rs b/hammond-gtk/src/app.rs index a2317f9..8d89c47 100644 --- a/hammond-gtk/src/app.rs +++ b/hammond-gtk/src/app.rs @@ -5,7 +5,6 @@ use gio::{ SimpleAction, SimpleActionExt, }; use glib; -use gst_player; use gtk; use gtk::prelude::*; use gtk::SettingsExt as GtkSettingsExt; @@ -19,7 +18,6 @@ use stacks::{Content, PopulatedState}; use utils; use widgets::appnotif::{InAppNotification, UndoState}; use widgets::player; -use widgets::player::PlayerExt; use widgets::{about_dialog, mark_all_notif, remove_show_notif}; use std::rc::Rc; @@ -57,9 +55,6 @@ pub enum Action { RemoveShow(Arc), ErrorNotification(String), InitEpisode(i32), - PlayerDurationChanged(player::Duration), - PlayerPositionUpdated(player::Position), - PlayerEndofStream(gst_player::Player), } #[derive(Debug)] @@ -138,7 +133,7 @@ impl App { window.show_all(); window.activate(); - gtk::timeout_add(15, clone!(sender, receiver => move || { + gtk::timeout_add(25, clone!(sender, receiver => move || { // Uses receiver, content, header, sender, overlay, playback match receiver.try_recv() { Ok(Action::RefreshAllViews) => content.update(), @@ -196,9 +191,6 @@ impl App { notif.show(&overlay); }, Ok(Action::InitEpisode(rowid)) => player.initialize_episode(rowid).unwrap(), - Ok(Action::PlayerDurationChanged(dur)) => player.timer.on_duration_changed(dur), - Ok(Action::PlayerPositionUpdated(pos)) => player.timer.on_position_updated(pos), - Ok(Action::PlayerEndofStream(_)) => player.stop(), Err(_) => (), } diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index eaa28d2..0dc73a5 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -11,6 +11,7 @@ use glib::SignalHandlerId; use chrono::NaiveTime; use crossbeam_channel::Sender; use failure::Error; +use send_cell::SendCell; use hammond_data::{dbqueries, USER_AGENT}; use hammond_data::{EpisodeWidgetQuery, PodcastCoverQuery}; @@ -23,12 +24,12 @@ use std::path::Path; use std::rc::Rc; #[derive(Debug, Clone, Copy)] -pub enum SeekDirection { +enum SeekDirection { Backwards, Forward, } -pub trait PlayerExt { +trait PlayerExt { fn play(&self); fn pause(&self); fn stop(&self); @@ -72,7 +73,7 @@ impl PlayerInfo { } #[derive(Debug, Clone)] -pub struct PlayerTimes { +struct PlayerTimes { container: gtk::Box, progressed: gtk::Label, duration: gtk::Label, @@ -82,7 +83,7 @@ pub struct PlayerTimes { } #[derive(Debug, Clone, Copy)] -pub struct Duration(ClockTime); +struct Duration(ClockTime); impl Deref for Duration { type Target = ClockTime; @@ -92,7 +93,7 @@ impl Deref for Duration { } #[derive(Debug, Clone, Copy)] -pub struct Position(ClockTime); +struct Position(ClockTime); impl Deref for Position { type Target = ClockTime; @@ -159,14 +160,19 @@ pub struct PlayerWidget { pub action_bar: gtk::ActionBar, player: gst_player::Player, controls: PlayerControls, - pub timer: PlayerTimes, + timer: PlayerTimes, info: PlayerInfo, rate: PlayerRate, } impl Default for PlayerWidget { fn default() -> Self { - let player = gst_player::Player::new(None, None); + let dispatcher = gst_player::PlayerGMainContextSignalDispatcher::new(None); + let player = gst_player::Player::new( + None, + // Use the gtk main thread + Some(&dispatcher.upcast::()), + ); let mut config = player.get_config(); config.set_user_agent(USER_AGENT); @@ -250,40 +256,10 @@ impl PlayerWidget { w } - #[cfg_attr(rustfmt, rustfmt_skip)] fn init(s: &Rc, sender: &Sender) { Self::connect_control_buttons(s); Self::connect_rate_buttons(s); - - // Log gst warnings. - s.player.connect_warning(move |_, warn| warn!("gst warning: {}", warn)); - - // Log gst errors. - s.player.connect_error(clone!(sender => move |_, error| { - // FIXME: should never occur and should not be user facing. - sender.send(Action::ErrorNotification(format!("Player Error: {}", error))) - .map_err(|err| error!("Error: {}", err)) - .ok(); - - })); - - s.player.connect_duration_changed(clone!(sender => move |_, clock| { - sender.send(Action::PlayerDurationChanged(Duration(clock))) - .map_err(|err| error!("Error: {}", err)) - .ok(); - })); - - s.player.connect_position_updated(clone!(sender => move |_, clock| { - sender.send(Action::PlayerPositionUpdated(Position(clock))) - .map_err(|err| error!("Error: {}", err)) - .ok(); - })); - - s.player.connect_end_of_stream(clone!(sender => move |player| { - sender.send(Action::PlayerEndofStream(player.clone())) - .map_err(|err| error!("Error: {}", err)) - .ok(); - })); + Self::connect_gst_signals(s, sender); } #[cfg_attr(rustfmt, rustfmt_skip)] @@ -302,6 +278,37 @@ impl PlayerWidget { s.controls.forward.connect_clicked(clone!(s => move |_| s.fast_forward())); } + #[cfg_attr(rustfmt, rustfmt_skip)] + fn connect_gst_signals(s: &Rc, sender: &Sender) { + // Log gst warnings. + s.player.connect_warning(move |_, warn| warn!("gst warning: {}", warn)); + + // Log gst errors. + s.player.connect_error(clone!(sender => move |_, error| { + // FIXME: should never occur and should not be user facing. + sender.send(Action::ErrorNotification(format!("Player Error: {}", error))) + .map_err(|err| error!("Error: {}", err)) + .ok(); + + })); + + // The followign callbacks require `Send` but are handled by the gtk main loop + let s2 = SendCell::new(s.clone()); + + // Update the duration label and the slider + s.player.connect_duration_changed(clone!(s2 => move |_, clock| { + s2.borrow().timer.on_duration_changed(Duration(clock)); + })); + + // Update the position label and the slider + s.player.connect_position_updated(clone!(s2 => move |_, clock| { + s2.borrow().timer.on_position_updated(Position(clock)); + })); + + // Reset the slider to 0 and show a play button + s.player.connect_end_of_stream(clone!(s2 => move |_| s2.borrow().stop())); + } + #[cfg_attr(rustfmt, rustfmt_skip)] fn connect_rate_buttons(s: &Rc) { s.rate.radio_normal.connect_toggled(clone!(s => move |_| s.on_rate_changed(1.00))); From faeafc329c9ace27678e38295ac3e99b30db75cc Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Sat, 23 Jun 2018 16:08:24 +0300 Subject: [PATCH 45/45] PlayerWidget: Tweak rewind on pause behavior. Only rewind on pause if the stream position is passed a certain point. Else it can feel a bit weird if you just started the stream and it immediatly rewinds. --- hammond-gtk/src/widgets/player.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/hammond-gtk/src/widgets/player.rs b/hammond-gtk/src/widgets/player.rs index 0dc73a5..4ec2362 100644 --- a/hammond-gtk/src/widgets/player.rs +++ b/hammond-gtk/src/widgets/player.rs @@ -375,7 +375,13 @@ impl PlayerExt for PlayerWidget { self.controls.play.show(); self.player.pause(); - self.seek(ClockTime::from_seconds(5), SeekDirection::Backwards); + + // Only rewind on pause if the stream position is passed a certain point. + if let Some(sec) = self.player.get_position().seconds() { + if sec >= 90 { + self.seek(ClockTime::from_seconds(5), SeekDirection::Backwards); + } + } } #[cfg_attr(rustfmt, rustfmt_skip)] @@ -385,6 +391,7 @@ impl PlayerExt for PlayerWidget { self.player.stop(); + // Reset the slider bar to the start self.timer.on_position_updated(Position(ClockTime::from_seconds(0))); }