[fractal/fractal-next] message-media: Use custom player
- From: Julian Sparber <jsparber src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal/fractal-next] message-media: Use custom player
- Date: Tue, 1 Feb 2022 08:28:51 +0000 (UTC)
commit cfe2fe0281a3e1dfd95beb2070fa91babcc05a56
Author: Kévin Commaille <zecakeh tedomum fr>
Date: Tue Feb 1 09:06:24 2022 +0100
message-media: Use custom player
Allows to play media without sound.
Closes #881
Cargo.lock | 67 +++++++++++++
Cargo.toml | 2 +
data/resources/ui/components-video-player.ui | 14 ++-
src/components/mod.rs | 2 +
src/components/video_player.rs | 110 ++++++++++++---------
src/components/video_player_renderer.rs | 79 +++++++++++++++
.../content/room_history/message_row/media.rs | 5 +-
7 files changed, 228 insertions(+), 51 deletions(-)
---
diff --git a/Cargo.lock b/Cargo.lock
index 6645fc9d..35236a1a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -539,6 +539,18 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17cc5e6b5ab06331c33589842070416baa137e8b0eb912b008cfd4a78ada7919"
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "winapi",
+]
+
[[package]]
name = "cipher"
version = "0.2.5"
@@ -971,8 +983,10 @@ dependencies = [
"ashpd",
"futures",
"gettext-rs",
+ "gst-plugin-gtk4",
"gstreamer",
"gstreamer-base",
+ "gstreamer-player",
"gstreamer-video",
"gtk-macros",
"gtk4",
@@ -1523,6 +1537,30 @@ dependencies = [
"system-deps 6.0.0",
]
+[[package]]
+name = "gst-plugin-gtk4"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f70501fa85dfdbeebecea35d747791351d04266f5c2c4aa23014d9fe74132290"
+dependencies = [
+ "fragile",
+ "gst-plugin-version-helper",
+ "gstreamer",
+ "gstreamer-base",
+ "gstreamer-video",
+ "gtk4",
+ "once_cell",
+]
+
+[[package]]
+name = "gst-plugin-version-helper"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a6a4dd1cb931cc6b49af354a68f21b3aee46b5b07370215d942f3a71542123f"
+dependencies = [
+ "chrono",
+]
+
[[package]]
name = "gstreamer"
version = "0.18.1"
@@ -1575,6 +1613,35 @@ dependencies = [
"system-deps 6.0.0",
]
+[[package]]
+name = "gstreamer-player"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f14ee02352ba73cadebe640bfb33f12fe8d03cbcad816a102d55a0251fb99bb"
+dependencies = [
+ "bitflags",
+ "glib",
+ "gstreamer",
+ "gstreamer-player-sys",
+ "gstreamer-video",
+ "libc",
+ "once_cell",
+]
+
+[[package]]
+name = "gstreamer-player-sys"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f9b674b39a4d0e18710f6e3d2b109f1793d8028ee4e39da3909b55b4529d399"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "gstreamer-sys",
+ "gstreamer-video-sys",
+ "libc",
+ "system-deps 6.0.0",
+]
+
[[package]]
name = "gstreamer-sys"
version = "0.18.0"
diff --git a/Cargo.toml b/Cargo.toml
index 65d0d9c4..c6e78d18 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -37,6 +37,8 @@ ashpd = { version = "0.2.0-beta-1", features = [
gst = { version = "0.18", package = "gstreamer" }
gst_base = { version = "0.18", package = "gstreamer-base" }
gst_video = { version = "0.18", package = "gstreamer-video" }
+gst_player = { version = "0.18", package = "gstreamer-player" }
+gst_gtk = { version = "0.1.0", package = "gst-plugin-gtk4" }
image = { version = "0.23", default-features = false, features = ["png"] }
regex = "1.5.4"
mime_guess = "2.0.3"
diff --git a/data/resources/ui/components-video-player.ui b/data/resources/ui/components-video-player.ui
index a1d664dc..35dd617d 100644
--- a/data/resources/ui/components-video-player.ui
+++ b/data/resources/ui/components-video-player.ui
@@ -1,10 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
+ <object class="GstPlayer" id="player">
+ <property name="video-renderer">
+ <object class="ComponentsVideoPlayerRenderer" id="video_renderer"/>
+ </property>
+ <property name="signal-dispatcher">
+ <object class="GstPlayerGMainContextSignalDispatcher"/>
+ </property>
+ <signal name="duration-changed" handler="duration_changed" swapped="true"/>
+ </object>
<template class="ComponentsVideoPlayer" parent="AdwBin">
<child>
<object class="GtkOverlay">
<child>
- <object class="GtkPicture" id="video"/>
+ <object class="GtkPicture" id="video">
+ <property name="paintable" bind-source="video_renderer" bind-property="paintable"
bind-flags="sync-create"/>
+ </object>
</child>
<child type="overlay">
<object class="GtkLabel" id="timestamp">
@@ -17,7 +28,6 @@
<property name="valign">GTK_ALIGN_START</property>
<property name="margin-start">5</property>
<property name="margin-top">5</property>
- <property name="label">00:00</property>
<layout>
<property name="measure">true</property>
</layout>
diff --git a/src/components/mod.rs b/src/components/mod.rs
index 098e7bd1..ba7c9f5e 100644
--- a/src/components/mod.rs
+++ b/src/components/mod.rs
@@ -11,6 +11,7 @@ mod reaction_chooser;
mod room_title;
mod spinner_button;
mod video_player;
+mod video_player_renderer;
pub use self::{
auth_dialog::{AuthData, AuthDialog},
@@ -26,4 +27,5 @@ pub use self::{
room_title::RoomTitle,
spinner_button::SpinnerButton,
video_player::VideoPlayer,
+ video_player_renderer::VideoPlayerRenderer,
};
diff --git a/src/components/video_player.rs b/src/components/video_player.rs
index 541cabe0..76ab33b4 100644
--- a/src/components/video_player.rs
+++ b/src/components/video_player.rs
@@ -1,5 +1,9 @@
use adw::subclass::prelude::*;
-use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
+use gst::ClockTime;
+use gst_player::{Player, PlayerGMainContextSignalDispatcher};
+use gtk::{gio, glib, prelude::*, subclass::prelude::*, CompositeTemplate};
+
+use super::VideoPlayerRenderer;
mod imp {
use std::cell::{Cell, RefCell};
@@ -19,6 +23,8 @@ mod imp {
pub video: TemplateChild<gtk::Picture>,
#[template_child]
pub timestamp: TemplateChild<gtk::Label>,
+ #[template_child]
+ pub player: TemplateChild<Player>,
}
#[glib::object_subclass]
@@ -28,7 +34,11 @@ mod imp {
type ParentType = adw::Bin;
fn class_init(klass: &mut Self::Class) {
+ VideoPlayerRenderer::static_type();
+ Player::static_type();
+ PlayerGMainContextSignalDispatcher::static_type();
Self::bind_template(klass);
+ Self::Type::bind_template_callbacks(klass);
}
fn instance_init(obj: &InitializingObject<Self>) {
@@ -39,13 +49,22 @@ mod imp {
impl ObjectImpl for VideoPlayer {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
- vec![glib::ParamSpecBoolean::new(
- "compact",
- "Compact",
- "Whether this player should be displayed in a compact format",
- false,
- glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
- )]
+ vec![
+ glib::ParamSpecBoolean::new(
+ "compact",
+ "Compact",
+ "Whether this player should be displayed in a compact format",
+ false,
+ glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
+ ),
+ glib::ParamSpecObject::new(
+ "player",
+ "Player",
+ "The GStreamerPlayer for the video",
+ Player::static_type(),
+ glib::ParamFlags::READABLE,
+ ),
+ ]
});
PROPERTIES.as_ref()
@@ -67,6 +86,7 @@ mod imp {
fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"compact" => obj.compact().to_value(),
+ "player" => obj.player().to_value(),
_ => unimplemented!(),
}
}
@@ -78,11 +98,12 @@ mod imp {
}
glib::wrapper! {
- /// A widget displaying a video media file.
+ /// A widget to preview a video media file without controls or sound.
pub struct VideoPlayer(ObjectSubclass<imp::VideoPlayer>)
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
}
+#[gtk::template_callbacks]
impl VideoPlayer {
/// Create a new video player.
#[allow(clippy::new_without_default)]
@@ -90,6 +111,10 @@ impl VideoPlayer {
glib::Object::new(&[]).expect("Failed to create VideoPlayer")
}
+ pub fn player(&self) -> &Player {
+ &*self.imp().player
+ }
+
pub fn compact(&self) -> bool {
self.imp().compact.get()
}
@@ -103,43 +128,38 @@ impl VideoPlayer {
self.notify("compact");
}
- /// Set the media_file to display.
- pub fn set_media_file(&self, media_file: >k::MediaFile) {
- let priv_ = self.imp();
-
- if let Some(handler_id) = priv_.duration_handler.take() {
- if let Some(paintable) = priv_.video.paintable() {
- paintable.disconnect(handler_id);
- }
- }
-
- priv_.video.set_paintable(Some(media_file));
- let timestamp = &*priv_.timestamp;
- let handler_id =
- media_file.connect_duration_notify(clone!(@weak timestamp => move |media_file| {
- timestamp.set_label(&duration(media_file));
- }));
- priv_.duration_handler.replace(Some(handler_id));
+ /// Set the file to display.
+ pub fn play_media_file(&self, file: &gio::File) {
+ self.duration_changed(None);
+ let player = self.player();
+ player.set_uri(Some(file.uri().as_ref()));
+ player.set_audio_track_enabled(false);
+ player.play();
}
-}
-/// Get the duration of `media_file` as a `String`.
-fn duration(media_file: >k::MediaFile) -> String {
- let mut time = media_file.duration() / 1000000;
-
- let sec = time % 60;
- time -= sec;
- let min = (time % (60 * 60)) / 60;
- time -= min * 60;
- let hour = time / (60 * 60);
-
- if hour > 0 {
- // FIXME: Find how to localize this.
- // hour:minutes:seconds
- format!("{}:{:02}:{:02}", hour, min, sec)
- } else {
- // FIXME: Find how to localize this.
- // minutes:seconds
- format!("{:02}:{:02}", min, sec)
+ #[template_callback]
+ fn duration_changed(&self, duration: Option<ClockTime>) {
+ let label = if let Some(duration) = duration {
+ let mut time = duration.seconds();
+
+ let sec = time % 60;
+ time -= sec;
+ let min = (time % (60 * 60)) / 60;
+ time -= min * 60;
+ let hour = time / (60 * 60);
+
+ if hour > 0 {
+ // FIXME: Find how to localize this.
+ // hour:minutes:seconds
+ format!("{}:{:02}:{:02}", hour, min, sec)
+ } else {
+ // FIXME: Find how to localize this.
+ // minutes:seconds
+ format!("{:02}:{:02}", min, sec)
+ }
+ } else {
+ "--:--".to_owned()
+ };
+ self.imp().timestamp.set_label(&label);
}
}
diff --git a/src/components/video_player_renderer.rs b/src/components/video_player_renderer.rs
new file mode 100644
index 00000000..e610c802
--- /dev/null
+++ b/src/components/video_player_renderer.rs
@@ -0,0 +1,79 @@
+use adw::subclass::prelude::*;
+use gst_gtk::PaintableSink;
+use gst_player::{subclass::prelude::*, Player, PlayerVideoRenderer};
+use gtk::{gdk, glib, prelude::*};
+
+mod imp {
+ use once_cell::{sync::Lazy, unsync::OnceCell};
+
+ use super::*;
+
+ #[derive(Debug, Default)]
+ pub struct VideoPlayerRenderer {
+ /// The sink to use to display the video.
+ pub sink: OnceCell<PaintableSink>,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for VideoPlayerRenderer {
+ const NAME: &'static str = "ComponentsVideoPlayerRenderer";
+ type Type = super::VideoPlayerRenderer;
+ type ParentType = glib::Object;
+ type Interfaces = (PlayerVideoRenderer,);
+ }
+
+ impl ObjectImpl for VideoPlayerRenderer {
+ fn properties() -> &'static [glib::ParamSpec] {
+ static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+ vec![glib::ParamSpecObject::new(
+ "paintable",
+ "Paintable",
+ "Paintable to render the video into",
+ gdk::Paintable::static_type(),
+ glib::ParamFlags::READABLE,
+ )]
+ });
+
+ PROPERTIES.as_ref()
+ }
+
+ fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+ match pspec.name() {
+ "paintable" => obj.paintable().to_value(),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn constructed(&self, obj: &Self::Type) {
+ obj.imp().sink.set(PaintableSink::new(None)).unwrap();
+ }
+ }
+
+ impl PlayerVideoRendererImpl for VideoPlayerRenderer {
+ fn create_video_sink(&self, _obj: &Self::Type, _player: &Player) -> gst::Element {
+ self.sink.get().unwrap().to_owned().upcast()
+ }
+ }
+}
+
+glib::wrapper! {
+ /// A widget displaying a video media file.
+ pub struct VideoPlayerRenderer(ObjectSubclass<imp::VideoPlayerRenderer>)
+ @implements PlayerVideoRenderer;
+}
+
+impl VideoPlayerRenderer {
+ pub fn new() -> Self {
+ glib::Object::new(&[]).expect("Failed to create VideoPlayerRenderer")
+ }
+
+ pub fn paintable(&self) -> gdk::Paintable {
+ self.imp().sink.get().unwrap().property("paintable")
+ }
+}
+
+impl Default for VideoPlayerRenderer {
+ fn default() -> Self {
+ Self::new()
+ }
+}
diff --git a/src/session/content/room_history/message_row/media.rs
b/src/session/content/room_history/message_row/media.rs
index f515d285..3a24a1f6 100644
--- a/src/session/content/room_history/message_row/media.rs
+++ b/src/session/content/room_history/message_row/media.rs
@@ -461,9 +461,6 @@ impl MessageMedia {
gio::Cancellable::NONE,
)
.unwrap();
- let media_file = gtk::MediaFile::for_file(&file);
- media_file.set_muted(true);
- media_file.connect_prepared_notify(|media_file| media_file.play());
let child = if let Some(Ok(child)) =
priv_.media.child().map(|w| w.downcast::<VideoPlayer>())
@@ -475,7 +472,7 @@ impl MessageMedia {
child
};
child.set_compact(obj.compact());
- child.set_media_file(&media_file)
+ child.play_media_file(&file)
}
};
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]