[fractal/fractal-next] room-history: Show video messages in the timeline
- From: Julian Sparber <jsparber src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal/fractal-next] room-history: Show video messages in the timeline
- Date: Thu, 9 Dec 2021 13:00:08 +0000 (UTC)
commit 8193be8e2c8352a4c8e777880f7ee3f358a9b3cf
Author: Kévin Commaille <zecakeh tedomum fr>
Date: Thu Dec 9 12:06:11 2021 +0100
room-history: Show video messages in the timeline
build-aux/org.gnome.FractalNext.Devel.json | 1 +
data/resources/resources.gresource.xml | 1 +
data/resources/style.css | 5 +
data/resources/ui/components-video-player.ui | 32 +++
data/resources/ui/media-viewer.ui | 4 +-
po/POTFILES.in | 3 +
src/components/mod.rs | 2 +
src/components/video_player.rs | 84 ++++++++
src/meson.build | 2 +
.../content/room_history/message_row/image.rs | 48 +----
.../content/room_history/message_row/mod.rs | 8 +-
.../content/room_history/message_row/video.rs | 233 +++++++++++++++++++++
src/session/media_viewer.rs | 63 +++++-
src/session/room/event.rs | 12 +-
src/utils.rs | 17 ++
15 files changed, 470 insertions(+), 45 deletions(-)
---
diff --git a/build-aux/org.gnome.FractalNext.Devel.json b/build-aux/org.gnome.FractalNext.Devel.json
index 6c3c8f98..5fbafef2 100644
--- a/build-aux/org.gnome.FractalNext.Devel.json
+++ b/build-aux/org.gnome.FractalNext.Devel.json
@@ -11,6 +11,7 @@
"finish-args" : [
"--socket=fallback-x11",
"--socket=wayland",
+ "--socket=pulseaudio",
"--share=network",
"--share=ipc",
"--device=dri",
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index 8840f519..e9d7f1b8 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -54,6 +54,7 @@
<file compressed="true" preprocess="xml-stripblanks"
alias="verification-emoji.ui">ui/verification-emoji.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="incoming-verification.ui">ui/incoming-verification.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="qr-code-scanner.ui">ui/qr-code-scanner.ui</file>
+ <file compressed="true" preprocess="xml-stripblanks"
alias="components-video-player.ui">ui/components-video-player.ui</file>
<file compressed="true">style.css</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/send-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/status/welcome.svg</file>
diff --git a/data/resources/style.css b/data/resources/style.css
index 23b12be7..6b4a32cb 100644
--- a/data/resources/style.css
+++ b/data/resources/style.css
@@ -214,6 +214,11 @@ headerbar.flat {
border-radius: 6px;
}
+.room-history .event-content .thumbnail .timestamp {
+ border-radius: 4px;
+ padding: 2px 5px;
+}
+
.room-history .event-content .quote {
border-left: 2px solid @theme_selected_bg_color;
padding-left: 6px;
diff --git a/data/resources/ui/components-video-player.ui b/data/resources/ui/components-video-player.ui
new file mode 100644
index 00000000..85dfc25d
--- /dev/null
+++ b/data/resources/ui/components-video-player.ui
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="ComponentsVideoPlayer" parent="AdwBin">
+ <style>
+ <class name="thumbnail"/>
+ </style>
+ <property name="overflow">hidden</property>
+ <child>
+ <object class="GtkOverlay">
+ <child>
+ <object class="GtkPicture" id="video"/>
+ </child>
+ <child type="overlay">
+ <object class="GtkLabel" id="timestamp">
+ <style>
+ <class name="osd"/>
+ <class name="timestamp"/>
+ </style>
+ <property name="halign">1</property>
+ <property name="valign">1</property>
+ <property name="margin-start">5</property>
+ <property name="margin-top">5</property>
+ <property name="label">00:00</property>
+ <layout>
+ <property name="measure">1</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/data/resources/ui/media-viewer.ui b/data/resources/ui/media-viewer.ui
index a3bccafb..3102ddc3 100644
--- a/data/resources/ui/media-viewer.ui
+++ b/data/resources/ui/media-viewer.ui
@@ -24,7 +24,7 @@
<child type="start">
<object class="GtkButton" id="back">
<property name="icon-name">go-previous-symbolic</property>
- <property name="action-name">session.show-content</property>
+ <property name="action-name">media-viewer.close</property>
</object>
</child>
<child type="end">
@@ -44,6 +44,8 @@
<child>
<object class="AdwBin" id="media">
<property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="vexpand">true</property>
</object>
</child>
</object>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index ff8fc197..9bba3ee6 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -12,6 +12,7 @@ data/resources/ui/account-settings-devices-page.ui
data/resources/ui/components-auth-dialog.ui
data/resources/ui/components-avatar.ui
data/resources/ui/components-loading-listbox-row.ui
+data/resources/ui/components-video-player.ui
data/resources/ui/avatar-with-selection.ui
data/resources/ui/content-divider-row.ui
data/resources/ui/content-item.ui
@@ -58,6 +59,7 @@ src/components/in_app_notification.rs
src/components/mod.rs
src/components/spinner_button.rs
src/components/pill.rs
+src/components/video_player.rs
src/contrib/mod.rs
src/contrib/qr_code.rs
src/error.rs
@@ -89,6 +91,7 @@ src/session/content/room_history/message_row/file.rs
src/session/content/room_history/message_row/image.rs
src/session/content/room_history/message_row/mod.rs
src/session/content/room_history/message_row/text.rs
+src/session/content/room_history/message_row/video.rs
src/session/content/room_history/mod.rs
src/session/content/room_history/state_row.rs
src/session/content/room_history/state_row/mod.rs
diff --git a/src/components/mod.rs b/src/components/mod.rs
index 5ea7aa17..5c005a97 100644
--- a/src/components/mod.rs
+++ b/src/components/mod.rs
@@ -9,6 +9,7 @@ mod loading_listbox_row;
mod pill;
mod room_title;
mod spinner_button;
+mod video_player;
pub use self::auth_dialog::{AuthData, AuthDialog};
pub use self::avatar::Avatar;
@@ -21,3 +22,4 @@ pub use self::loading_listbox_row::LoadingListBoxRow;
pub use self::pill::Pill;
pub use self::room_title::RoomTitle;
pub use self::spinner_button::SpinnerButton;
+pub use self::video_player::VideoPlayer;
diff --git a/src/components/video_player.rs b/src/components/video_player.rs
new file mode 100644
index 00000000..7cd04b67
--- /dev/null
+++ b/src/components/video_player.rs
@@ -0,0 +1,84 @@
+use adw::subclass::prelude::*;
+use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
+
+mod imp {
+ use super::*;
+ use glib::subclass::InitializingObject;
+ use std::cell::RefCell;
+
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/org/gnome/FractalNext/components-video-player.ui")]
+ pub struct VideoPlayer {
+ pub media_file: RefCell<Option<gtk::MediaFile>>,
+ #[template_child]
+ pub video: TemplateChild<gtk::Picture>,
+ #[template_child]
+ pub timestamp: TemplateChild<gtk::Label>,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for VideoPlayer {
+ const NAME: &'static str = "ComponentsVideoPlayer";
+ type Type = super::VideoPlayer;
+ type ParentType = adw::Bin;
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+ }
+
+ fn instance_init(obj: &InitializingObject<Self>) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for VideoPlayer {}
+
+ impl WidgetImpl for VideoPlayer {}
+
+ impl BinImpl for VideoPlayer {}
+}
+
+glib::wrapper! {
+ /// A widget displaying a video media file.
+ pub struct VideoPlayer(ObjectSubclass<imp::VideoPlayer>)
+ @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+}
+
+impl VideoPlayer {
+ pub fn new(media_file: >k::MediaFile) -> Self {
+ let self_: Self = glib::Object::new(&[]).expect("Failed to create VideoPlayer");
+ self_.build(media_file);
+ self_
+ }
+
+ pub fn build(&self, media_file: >k::MediaFile) {
+ let priv_ = imp::VideoPlayer::from_instance(self);
+
+ priv_.video.set_paintable(Some(media_file));
+ let timestamp = &*priv_.timestamp;
+ media_file.connect_duration_notify(clone!(@weak timestamp => move |media_file| {
+ timestamp.set_label(&duration(media_file));
+ }));
+ }
+}
+
+/// 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 = time - sec;
+ let min = (time % (60 * 60)) / 60;
+ time = 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)
+ }
+}
diff --git a/src/meson.build b/src/meson.build
index 5b1a394d..a4e93ffb 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -37,6 +37,7 @@ sources = files(
'components/in_app_notification.rs',
'components/spinner_button.rs',
'components/loading_listbox_row.rs',
+ 'components/video_player.rs',
'config.rs',
'error.rs',
'main.rs',
@@ -66,6 +67,7 @@ sources = files(
'session/content/room_history/message_row/image.rs',
'session/content/room_history/message_row/mod.rs',
'session/content/room_history/message_row/text.rs',
+ 'session/content/room_history/message_row/video.rs',
'session/content/room_history/mod.rs',
'session/content/room_history/state_row/creation.rs',
'session/content/room_history/state_row/mod.rs',
diff --git a/src/session/content/room_history/message_row/image.rs
b/src/session/content/room_history/message_row/image.rs
index 29f5496e..8a46e784 100644
--- a/src/session/content/room_history/message_row/image.rs
+++ b/src/session/content/room_history/message_row/image.rs
@@ -1,5 +1,3 @@
-use std::convert::TryInto;
-
use adw::{prelude::BinExt, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{
@@ -15,15 +13,12 @@ use matrix_sdk::{
media::{MediaEventContent, MediaThumbnailSize},
ruma::{
api::client::r0::media::get_content_thumbnail::Method,
- events::{
- room::{message::ImageMessageEventContent, ImageInfo},
- sticker::StickerEventContent,
- },
+ events::{room::message::ImageMessageEventContent, sticker::StickerEventContent},
uint,
},
};
-use crate::{session::Session, spawn, spawn_tokio};
+use crate::{session::Session, spawn, spawn_tokio, utils::uint_to_i32};
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::GEnum)]
#[repr(u32)]
@@ -222,7 +217,9 @@ glib::wrapper! {
impl MessageImage {
pub fn image(image: ImageMessageEventContent, session: &Session) -> Self {
- let (width, height) = get_width_height(image.info.as_deref());
+ let info = image.info.as_deref();
+ let width = uint_to_i32(info.and_then(|info| info.width));
+ let height = uint_to_i32(info.and_then(|info| info.height));
let self_: Self = glib::Object::new(&[("width", &width), ("height", &height)])
.expect("Failed to create MessageImage");
@@ -231,7 +228,9 @@ impl MessageImage {
}
pub fn sticker(sticker: StickerEventContent, session: &Session) -> Self {
- let (width, height) = get_width_height(Some(&sticker.info));
+ let info = &sticker.info;
+ let width = uint_to_i32(info.width);
+ let height = uint_to_i32(info.height);
let self_: Self = glib::Object::new(&[
("media-type", &MediaType::Sticker),
@@ -319,34 +318,3 @@ impl MessageImage {
);
}
}
-
-/// Gets the width and height of the full image in info.
-///
-/// Returns a (width, height) tuple with either value set to -1 if it wasn't found.
-fn get_width_height(info: Option<&ImageInfo>) -> (i32, i32) {
- let width = info
- .and_then(|info| info.width)
- .and_then(|ui| {
- let u: Option<u16> = ui.try_into().ok();
- u
- })
- .and_then(|u| {
- let i: i32 = u.into();
- Some(i)
- })
- .unwrap_or(-1);
-
- let height = info
- .and_then(|info| info.height)
- .and_then(|ui| {
- let u: Option<u16> = ui.try_into().ok();
- u
- })
- .and_then(|u| {
- let i: i32 = u.into();
- Some(i)
- })
- .unwrap_or(-1);
-
- (width, height)
-}
diff --git a/src/session/content/room_history/message_row/mod.rs
b/src/session/content/room_history/message_row/mod.rs
index 9e88e210..d39e9b63 100644
--- a/src/session/content/room_history/message_row/mod.rs
+++ b/src/session/content/room_history/message_row/mod.rs
@@ -1,6 +1,7 @@
mod file;
mod image;
mod text;
+mod video;
use crate::components::Avatar;
use adw::{prelude::*, subclass::prelude::*};
@@ -15,7 +16,7 @@ use matrix_sdk::ruma::events::{
AnyMessageEventContent, AnySyncMessageEvent, AnySyncRoomEvent,
};
-use self::{file::MessageFile, image::MessageImage, text::MessageText};
+use self::{file::MessageFile, image::MessageImage, text::MessageText, video::MessageVideo};
use crate::prelude::*;
use crate::session::room::Event;
@@ -285,7 +286,10 @@ impl MessageRow {
let child = MessageText::markup(message.formatted, message.body);
priv_.content.set_child(Some(&child));
}
- MessageType::Video(_message) => {}
+ MessageType::Video(message) => {
+ let child = MessageVideo::new(message, &event.room().session());
+ priv_.content.set_child(Some(&child));
+ }
MessageType::VerificationRequest(_message) => {}
_ => {
warn!("Event not supported: {:?}", msgtype)
diff --git a/src/session/content/room_history/message_row/video.rs
b/src/session/content/room_history/message_row/video.rs
new file mode 100644
index 00000000..d88c1058
--- /dev/null
+++ b/src/session/content/room_history/message_row/video.rs
@@ -0,0 +1,233 @@
+use adw::{prelude::BinExt, subclass::prelude::*};
+use gettextrs::gettext;
+use gtk::{
+ gio,
+ glib::{self, clone},
+ prelude::*,
+ subclass::prelude::*,
+};
+use log::warn;
+use matrix_sdk::ruma::events::room::message::VideoMessageEventContent;
+
+use crate::{
+ components::VideoPlayer,
+ session::Session,
+ spawn, spawn_tokio,
+ utils::{cache_dir, uint_to_i32},
+};
+
+mod imp {
+ use std::cell::Cell;
+
+ use once_cell::sync::Lazy;
+
+ use super::*;
+
+ #[derive(Debug, Default)]
+ pub struct MessageVideo {
+ /// The intended display width of the video.
+ pub width: Cell<i32>,
+ /// The intended display height of the video.
+ pub height: Cell<i32>,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for MessageVideo {
+ const NAME: &'static str = "ContentMessageVideo";
+ type Type = super::MessageVideo;
+ type ParentType = adw::Bin;
+ }
+
+ impl ObjectImpl for MessageVideo {
+ fn properties() -> &'static [glib::ParamSpec] {
+ static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+ vec![
+ glib::ParamSpec::new_int(
+ "width",
+ "Width",
+ "The intended display width of the video",
+ -1,
+ i32::MAX,
+ -1,
+ glib::ParamFlags::WRITABLE,
+ ),
+ glib::ParamSpec::new_int(
+ "height",
+ "Height",
+ "The intended display height of the video",
+ -1,
+ i32::MAX,
+ -1,
+ glib::ParamFlags::WRITABLE,
+ ),
+ ]
+ });
+
+ PROPERTIES.as_ref()
+ }
+
+ fn set_property(
+ &self,
+ _obj: &Self::Type,
+ _id: usize,
+ value: &glib::Value,
+ pspec: &glib::ParamSpec,
+ ) {
+ match pspec.name() {
+ "width" => {
+ self.width.set(value.get().unwrap());
+ }
+ "height" => {
+ self.height.set(value.get().unwrap());
+ }
+ _ => unimplemented!(),
+ }
+ }
+
+ fn constructed(&self, obj: &Self::Type) {
+ self.parent_constructed(obj);
+
+ // We need to control the value returned by `measure`.
+ obj.set_layout_manager(gtk::NONE_LAYOUT_MANAGER);
+ }
+ }
+
+ impl WidgetImpl for MessageVideo {
+ fn measure(
+ &self,
+ obj: &Self::Type,
+ orientation: gtk::Orientation,
+ for_size: i32,
+ ) -> (i32, i32, i32, i32) {
+ match obj.child() {
+ Some(child) => {
+ let original_width = self.width.get();
+ let original_height = self.height.get();
+
+ if orientation == gtk::Orientation::Vertical {
+ // We limit the width to 320 pixels.
+ let width = for_size.min(320);
+
+ let nat_height = if original_height > 0 && original_width > 0 {
+ // We don't want the paintable to be upscaled.
+ let width = width.min(original_width);
+ width * original_height / original_width
+ } else {
+ // Get the natural height of the data.
+ child.measure(orientation, width).1
+ };
+
+ // We limit the height to 240 pixels.
+ let height = nat_height.min(240);
+ (0, height, -1, -1)
+ } else {
+ // We limit the height to 240 pixels.
+ let height = for_size.min(240);
+
+ let nat_width = if original_height > 0 && original_width > 0 {
+ // We don't want the paintable to be upscaled.
+ let height = height.min(original_height);
+ height * original_width / original_height
+ } else {
+ // Get the natural height of the data.
+ child.measure(orientation, height).1
+ };
+
+ // We limit the width to 320 pixels.
+ let width = nat_width.min(320);
+ (0, width, -1, -1)
+ }
+ }
+ None => (0, 0, -1, -1),
+ }
+ }
+
+ fn request_mode(&self, _obj: &Self::Type) -> gtk::SizeRequestMode {
+ gtk::SizeRequestMode::HeightForWidth
+ }
+
+ fn size_allocate(&self, obj: &Self::Type, _width: i32, height: i32, baseline: i32) {
+ if let Some(child) = obj.child() {
+ // We need to allocate just enough width to the child so it doesn't expand.
+ let original_width = self.width.get();
+ let original_height = self.height.get();
+ let width = if original_height > 0 && original_width > 0 {
+ height * original_width / original_height
+ } else {
+ // Get the natural width of the video data.
+ child.measure(gtk::Orientation::Horizontal, height).1
+ };
+
+ child.allocate(width, height, baseline, None);
+ }
+ }
+ }
+
+ impl BinImpl for MessageVideo {}
+}
+
+glib::wrapper! {
+ /// A widget displaying an message's thumbnail.
+ pub struct MessageVideo(ObjectSubclass<imp::MessageVideo>)
+ @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+}
+
+impl MessageVideo {
+ pub fn new(video: VideoMessageEventContent, session: &Session) -> Self {
+ let info = video.info.as_deref();
+ let width = uint_to_i32(info.and_then(|info| info.width));
+ let height = uint_to_i32(info.and_then(|info| info.height));
+
+ let self_: Self = glib::Object::new(&[("width", &width), ("height", &height)])
+ .expect("Failed to create MessageVideo");
+ self_.build(video, session);
+ self_
+ }
+
+ fn build(&self, video: VideoMessageEventContent, session: &Session) {
+ let body = video.body.clone();
+ let client = session.client();
+ let handle = spawn_tokio!(async move { client.get_file(video, true,).await });
+
+ spawn!(
+ glib::PRIORITY_LOW,
+ clone!(@weak self as obj => async move {
+ match handle.await.unwrap() {
+ Ok(Some(data)) => {
+ // The GStreamer backend of GtkVideo doesn't work with input streams so
+ // we need to store the file.
+ // See: https://gitlab.gnome.org/GNOME/gtk/-/issues/4062
+ let mut path = cache_dir();
+ path.push(body);
+ let file = gio::File::for_path(path);
+ file.replace_contents(
+ &data,
+ None,
+ false,
+ gio::FileCreateFlags::REPLACE_DESTINATION,
+ gio::NONE_CANCELLABLE,
+ )
+ .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 video_player = VideoPlayer::new(&media_file);
+
+ obj.set_child(Some(&video_player));
+ }
+ Ok(None) => {
+ warn!("Could not retrieve invalid image file");
+ let child = gtk::Label::new(Some(&gettext("Could not retrieve image")));
+ obj.set_child(Some(&child));
+ }
+ Err(error) => {
+ warn!("Could not retrieve image file: {}", error);
+ let child = gtk::Label::new(Some(&gettext("Could not retrieve image")));
+ obj.set_child(Some(&child));
+ }
+ }
+ })
+ );
+ }
+}
diff --git a/src/session/media_viewer.rs b/src/session/media_viewer.rs
index 28eda93e..9dd65536 100644
--- a/src/session/media_viewer.rs
+++ b/src/session/media_viewer.rs
@@ -9,7 +9,9 @@ use matrix_sdk::ruma::events::{room::message::MessageType, AnyMessageEventConten
use crate::{
components::{ContextMenuBin, ContextMenuBinImpl},
session::room::Event,
- spawn, Window,
+ spawn,
+ utils::cache_dir,
+ Window,
};
use super::room::EventActions;
@@ -45,6 +47,28 @@ mod imp {
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
+
+ klass.install_action("media-viewer.close", None, move |obj, _, _| {
+ let priv_ = imp::MediaViewer::from_instance(obj);
+ if let Some(stream) = priv_
+ .media
+ .child()
+ .and_then(|w| w.downcast::<gtk::Video>().ok())
+ .and_then(|video| video.media_stream())
+ {
+ if stream.is_playing() {
+ stream.pause();
+ stream.seek(0);
+ }
+ }
+ obj.activate_action("session.show-content", None);
+ });
+ klass.add_binding_action(
+ gdk::keys::constants::Escape,
+ gdk::ModifierType::empty(),
+ "media-viewer.close",
+ None,
+ );
}
fn instance_init(obj: &InitializingObject<Self>) {
@@ -264,6 +288,43 @@ impl MediaViewer {
})
);
}
+ MessageType::Video(video) => {
+ self.set_body(Some(video.body.clone()));
+
+ spawn!(
+ glib::PRIORITY_LOW,
+ clone!(@weak self as obj => async move {
+ let priv_ = imp::MediaViewer::from_instance(&obj);
+
+ match event.get_media_content().await {
+ Ok((_, data)) => {
+ // The GStreamer backend of GtkVideo doesn't work with input streams
so
+ // we need to store the file.
+ // See: https://gitlab.gnome.org/GNOME/gtk/-/issues/4062
+ let mut path = cache_dir();
+ path.push(video.body);
+ let file = gio::File::for_path(path);
+ file.replace_contents(
+ &data,
+ None,
+ false,
+ gio::FileCreateFlags::REPLACE_DESTINATION,
+ gio::NONE_CANCELLABLE,
+ )
+ .unwrap();
+ let child = gtk::Video::builder().file(&file).autoplay(true).build();
+
+ priv_.media.set_child(Some(&child));
+ }
+ Err(error) => {
+ warn!("Could not retrieve video file: {}", error);
+ let child = gtk::Label::new(Some(&gettext("Could not retrieve
video")));
+ priv_.media.set_child(Some(&child));
+ }
+ }
+ })
+ );
+ }
_ => {}
}
}
diff --git a/src/session/room/event.rs b/src/session/room/event.rs
index 9c63fb78..64141075 100644
--- a/src/session/room/event.rs
+++ b/src/session/room/event.rs
@@ -516,6 +516,7 @@ impl Event {
///
/// - File message (`MessageType::File`).
/// - Image message (`MessageType::Image`).
+ /// - Video message (`MessageType::Video`).
///
/// Returns `Ok((filename, binary_content))` on success, `Err` if an error occured while
/// fetching the content. Panics on an incompatible event.
@@ -535,6 +536,12 @@ impl Event {
let data = handle.await.unwrap()?.unwrap();
return Ok((filename, data));
}
+ MessageType::Video(content) => {
+ let filename = content.body.clone();
+ let handle = spawn_tokio!(async move { client.get_file(content, true).await });
+ let data = handle.await.unwrap()?.unwrap();
+ return Ok((filename, data));
+ }
_ => {}
};
};
@@ -546,7 +553,10 @@ impl Event {
pub fn can_view_media(&self) -> bool {
match self.message_content() {
Some(AnyMessageEventContent::RoomMessage(message)) => {
- matches!(message.msgtype, MessageType::Image(_))
+ matches!(
+ message.msgtype,
+ MessageType::Image(_) | MessageType::Video(_)
+ )
}
_ => false,
}
diff --git a/src/utils.rs b/src/utils.rs
index e4b336a2..447a0d25 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -58,10 +58,12 @@ macro_rules! spawn_tokio {
};
}
+use std::convert::TryInto;
use std::path::PathBuf;
use gtk::gio::{self, prelude::*};
use gtk::glib::{self, Object};
+use matrix_sdk::ruma::UInt;
/// Returns an expression looking up the given property on `object`.
pub fn prop_expr<T: IsA<Object>>(object: &T, prop: &str) -> gtk::Expression {
@@ -121,3 +123,18 @@ pub fn cache_dir() -> PathBuf {
path
}
+
+/// Converts a `UInt` to `i32`.
+///
+/// Returns `-1` if the conversion didn't work.
+pub fn uint_to_i32(u: Option<UInt>) -> i32 {
+ u.and_then(|ui| {
+ let u: Option<u16> = ui.try_into().ok();
+ u
+ })
+ .and_then(|u| {
+ let i: i32 = u.into();
+ Some(i)
+ })
+ .unwrap_or(-1)
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]