1#[cfg(feature = "audio")]
18mod backend {
19 use std::cell::RefCell;
20 use std::path::Path;
21 use std::rc::Rc;
22 use std::sync::Once;
23
24 use gstreamer as gst;
25 use gstreamer::prelude::*;
26 use relm4::gtk::{gio, glib};
27 use relm4::gtk::gio::prelude::FileExt;
28
29 static INIT: Once = Once::new();
30
31 pub struct AudioPlayer {
33 pipeline: gst::Element,
34 _bus_watch: RefCell<Option<gst::bus::BusWatchGuard>>,
36 }
37
38 impl AudioPlayer {
39 pub fn new(path: &Path) -> Option<Rc<Self>> {
43 INIT.call_once(|| {
44 let _ = gst::init();
45 });
46
47 let pipeline = match gst::ElementFactory::make("playbin").build() {
48 Ok(pipeline) => pipeline,
49 Err(err) => {
50 eprintln!("tree-space: playbin unavailable: {err}");
51 return None;
52 }
53 };
54 pipeline.set_property("uri", gio::File::for_path(path).uri().as_str());
55 pipeline.set_property("mute", false);
57 pipeline.set_property("volume", 1.0);
58
59 let player = Rc::new(AudioPlayer {
60 pipeline,
61 _bus_watch: RefCell::new(None),
62 });
63 player.install_bus_watch();
64 Some(player)
65 }
66
67 fn install_bus_watch(self: &Rc<Self>) {
70 let Some(bus) = self.pipeline.bus() else {
71 return;
72 };
73 let weak = Rc::downgrade(self);
74 let watch = bus.add_watch_local(move |_, message| {
75 use gst::MessageView;
76 let Some(player) = weak.upgrade() else {
77 return glib::ControlFlow::Break;
78 };
79 match message.view() {
80 MessageView::Eos(..) => {
81 let _ = player.pipeline.set_state(gst::State::Paused);
82 let _ = player.pipeline.seek_simple(
83 gst::SeekFlags::FLUSH | gst::SeekFlags::KEY_UNIT,
84 gst::ClockTime::ZERO,
85 );
86 }
87 MessageView::Error(err) => {
88 eprintln!("tree-space: audio playback error: {}", err.error());
89 }
90 _ => {}
91 }
92 glib::ControlFlow::Continue
93 });
94 if let Ok(guard) = watch {
95 *self._bus_watch.borrow_mut() = Some(guard);
96 }
97 }
98
99 pub fn is_playing(&self) -> bool {
100 self.pipeline.current_state() == gst::State::Playing
101 }
102
103 pub fn set_playing(&self, playing: bool) {
104 let state = if playing { gst::State::Playing } else { gst::State::Paused };
105 let _ = self.pipeline.set_state(state);
106 }
107
108 pub fn position(&self) -> i64 {
110 self.pipeline
111 .query_position::<gst::ClockTime>()
112 .map(|t| t.useconds() as i64)
113 .unwrap_or(0)
114 }
115
116 pub fn duration(&self) -> i64 {
118 self.pipeline
119 .query_duration::<gst::ClockTime>()
120 .map(|t| t.useconds() as i64)
121 .unwrap_or(0)
122 }
123
124 pub fn seek(&self, microseconds: i64) {
126 if microseconds <= 0 {
127 return;
128 }
129 let _ = self.pipeline.seek_simple(
130 gst::SeekFlags::FLUSH | gst::SeekFlags::KEY_UNIT,
131 gst::ClockTime::from_useconds(microseconds as u64),
132 );
133 }
134
135 pub fn set_volume(&self, volume: f64) {
136 self.pipeline.set_property("volume", volume.clamp(0.0, 1.0));
137 }
138
139 pub fn volume(&self) -> f64 {
140 self.pipeline.property::<f64>("volume")
141 }
142 }
143
144 impl Drop for AudioPlayer {
145 fn drop(&mut self) {
146 let _ = self.pipeline.set_state(gst::State::Null);
148 }
149 }
150}
151
152#[cfg(not(feature = "audio"))]
153mod backend {
154 use std::path::Path;
155 use std::rc::Rc;
156
157 pub struct AudioPlayer;
160
161 impl AudioPlayer {
162 pub fn new(_path: &Path) -> Option<Rc<Self>> {
163 None
164 }
165
166 pub fn is_playing(&self) -> bool {
167 false
168 }
169
170 pub fn set_playing(&self, _playing: bool) {}
171
172 pub fn position(&self) -> i64 {
173 0
174 }
175
176 pub fn duration(&self) -> i64 {
177 0
178 }
179
180 pub fn seek(&self, _microseconds: i64) {}
181
182 pub fn set_volume(&self, _volume: f64) {}
183
184 pub fn volume(&self) -> f64 {
185 1.0
186 }
187 }
188}
189
190pub use backend::AudioPlayer;