From c8743ae1719cab5c8ecb59ba859d8d4b7862959f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Tue, 14 Oct 2025 11:30:51 +0200 Subject: [PATCH] media: Send waveform with audio messages --- src/components/media/audio_player/mod.rs | 64 +++++++----------------- src/utils/media/audio.rs | 18 +++---- 2 files changed, 26 insertions(+), 56 deletions(-) diff --git a/src/components/media/audio_player/mod.rs b/src/components/media/audio_player/mod.rs index a461ab80..52205288 100644 --- a/src/components/media/audio_player/mod.rs +++ b/src/components/media/audio_player/mod.rs @@ -3,6 +3,7 @@ use std::time::Duration; use adw::{prelude::*, subclass::prelude::*}; use gettextrs::gettext; use gtk::{gio, glib, glib::clone}; +use matrix_sdk::attachment::BaseAudioInfo; use tracing::warn; mod waveform; @@ -16,10 +17,7 @@ use crate::{ utils::{ File, LoadingState, OneshotNotifier, matrix::{AudioMessageExt, MediaMessage, MessageCacheKey}, - media::{ - self, MediaFileError, - audio::{generate_waveform, load_audio_info}, - }, + media::{self, MediaFileError, audio::load_audio_info}, }, }; @@ -161,14 +159,7 @@ mod imp { #[weak(rename_to = imp)] self, async move { - imp.load_source_duration().await; - } - )); - spawn!(clone!( - #[weak(rename_to = imp)] - self, - async move { - imp.load_source_waveform().await; + imp.load_source_info().await; } )); @@ -261,26 +252,18 @@ mod imp { self.remaining_label.set_width_chars(time_width + 1); } - /// Load the duration of the current source. - async fn load_source_duration(&self) { + /// Load the information of the current source. + async fn load_source_info(&self) { let Some(source) = self.source.borrow().clone() else { self.set_duration(Duration::default()); - return; - }; - - let duration = source.duration().await; - self.set_duration(duration.unwrap_or_default()); - } - - /// Load the waveform of the current source. - async fn load_source_waveform(&self) { - let Some(source) = self.source.borrow().clone() else { self.waveform.set_waveform(vec![]); return; }; - let waveform = source.waveform().await; - self.waveform.set_waveform(waveform.unwrap_or_default()); + let info = source.info().await; + self.set_duration(info.duration.unwrap_or_default()); + self.waveform + .set_waveform(info.waveform.unwrap_or_default()); } /// Update the name of the source. @@ -437,7 +420,7 @@ mod imp { .borrow() .as_ref() .is_some_and(|source| matches!(source, AudioPlayerSource::Message(_))) - && let Some(waveform) = generate_waveform(&gfile).await + && let Some(waveform) = load_audio_info(&gfile).await.waveform { self.waveform.set_waveform(waveform); } @@ -564,30 +547,19 @@ impl AudioPlayerSource { } } - /// Get the duration of this source, if any. - async fn duration(&self) -> Option { + /// Get the information of this source. + async fn info(&self) -> BaseAudioInfo { match self { - Self::File(file) => load_audio_info(file).await.duration, + Self::File(file) => load_audio_info(file).await, Self::Message(message) => { - if let MediaMessage::Audio(content) = &message.message { - content.info.as_deref().and_then(|info| info.duration) - } else { - None - } - } - } - } + let mut info = BaseAudioInfo::default(); - /// Get the waveform representation of this source, if any. - async fn waveform(&self) -> Option> { - match self { - Self::File(file) => generate_waveform(file).await, - Self::Message(message) => { if let MediaMessage::Audio(content) = &message.message { - content.normalized_waveform() - } else { - None + info.duration = content.info.as_deref().and_then(|info| info.duration); + info.waveform = content.normalized_waveform(); } + + info } } } diff --git a/src/utils/media/audio.rs b/src/utils/media/audio.rs index 9d777e68..a7128682 100644 --- a/src/utils/media/audio.rs +++ b/src/utils/media/audio.rs @@ -17,11 +17,12 @@ use crate::utils::{OneshotNotifier, resample_slice}; pub(crate) async fn load_audio_info(file: &gio::File) -> BaseAudioInfo { let mut info = BaseAudioInfo::default(); - let Some(media_info) = load_gstreamer_media_info(file).await else { - return info; - }; + if let Some(media_info) = load_gstreamer_media_info(file).await { + info.duration = media_info.duration().map(Into::into); + } + + info.waveform = generate_waveform(file, info.duration).await; - info.duration = media_info.duration().map(Into::into); info } @@ -29,18 +30,15 @@ pub(crate) async fn load_audio_info(file: &gio::File) -> BaseAudioInfo { /// /// The returned waveform should contain between 30 and 110 samples with a value /// between 0 and 1. -pub(crate) async fn generate_waveform(file: &gio::File) -> Option> { +async fn generate_waveform(file: &gio::File, duration: Option) -> Option> { // We first need to get the duration, to compute the interval required to // collect just enough samples. We use a separate pipeline for simplicity, // but we could use the same pipeline and ignore the first run while we // collect the duration. - let interval = load_gstreamer_media_info(file) - .await - .and_then(|media_info| media_info.duration()) + let interval = duration // Take 110 samples, it should more or less match the maximum number of samples we present. - .and_then(|duration| Duration::from(duration).checked_div(110)) // Default to 10 samples per second. - .unwrap_or_else(|| Duration::from_millis(100)); + .map_or_else(|| Duration::from_millis(100), |duration| duration / 110); // Create our pipeline from a pipeline description string. let pipeline = match gst::parse::launch(&format!(