shits broken but i still gotta eat

This commit is contained in:
Erica Z 2024-11-04 12:31:43 +01:00
parent e2902e862d
commit ea31535bfb
6 changed files with 185 additions and 134 deletions

View file

@ -119,6 +119,8 @@ template $AudreyUiWindow: Adw.ApplicationWindow {
margin-end: 24; margin-end: 24;
styles [ "frame" ] styles [ "frame" ]
model: bind template.playlist_model;
playlist-pos: bind template.playlist-pos;
//playbin: bind template.playbin; //playbin: bind template.playbin;
} }
}; };
@ -130,6 +132,10 @@ template $AudreyUiWindow: Adw.ApplicationWindow {
song: bind template.song; song: bind template.song;
playing_cover_art: bind template.playing_cover_art; playing_cover_art: bind template.playing_cover_art;
show_cover_art: bind $show_playbar_cover_art (stack.visible-child-name) as <bool>; show_cover_art: bind $show_playbar_cover_art (stack.visible-child-name) as <bool>;
volume: bind template.volume bidirectional;
mute: bind template.mute bidirectional;
pause: bind template.pause bidirectional;
} }
} }
} }

View file

@ -127,7 +127,7 @@ impl Playbin {
"loadfile", "loadfile",
&entry.stream_url(), &entry.stream_url(),
"insert-at-play", "insert-at-play",
index.to_string(), &index.to_string(),
]) ])
.unwrap(); .unwrap();
entries.insert(index, entry); entries.insert(index, entry);

View file

@ -4,8 +4,8 @@ pub use song::Song;
mod imp { mod imp {
use crate::{Playbin, PlaybinSong}; use crate::{Playbin, PlaybinSong};
use adw::{gio, glib, prelude::*, subclass::prelude::*}; use adw::{gio, glib, prelude::*, subclass::prelude::*};
use glib::subclass::InitializingObject; use glib::{subclass::InitializingObject, WeakRef};
use std::cell::RefCell; use std::cell::{Cell, RefCell};
use std::rc::Rc; use std::rc::Rc;
use tracing::{event, Level}; use tracing::{event, Level};
@ -14,9 +14,9 @@ mod imp {
#[properties(wrapper_type = super::PlayQueue)] #[properties(wrapper_type = super::PlayQueue)]
pub struct PlayQueue { pub struct PlayQueue {
#[property(get, set)] #[property(get, set)]
pub(super) model: RefCell<Option<gio::ListStore>>, pub(super) model: WeakRef<gio::ListModel>,
#[property(get, set)]
pub(super) playbin: RefCell<Option<Rc<Playbin>>>, _playlist_pos: Cell<i64>,
} }
#[glib::object_subclass] #[glib::object_subclass]
@ -39,8 +39,6 @@ mod imp {
impl ObjectImpl for PlayQueue { impl ObjectImpl for PlayQueue {
fn constructed(&self) { fn constructed(&self) {
self.parent_constructed(); self.parent_constructed();
self.obj().set_model(gio::ListStore::new::<PlaybinSong>());
} }
} }
@ -61,13 +59,17 @@ mod imp {
#[template_callback] #[template_callback]
fn on_song_list_setup(&self, item: &gtk::ListItem, _factory: &gtk::SignalListItemFactory) { fn on_song_list_setup(&self, item: &gtk::ListItem, _factory: &gtk::SignalListItemFactory) {
let child = super::Song::new(Some(&self.obj().window())); let child: super::Song = glib::Object::new();
child.set_draggable(true); child.set_draggable(true);
child.set_show_position(true); child.set_show_position(true);
child.set_show_artist(true); child.set_show_artist(true);
child.set_show_cover(true); child.set_show_cover(true);
self.obj()
.bind_property("playlist-pos", &child, "playlist-pos")
.build();
item.set_child(Some(&child)); item.set_child(Some(&child));
} }
@ -87,11 +89,7 @@ mod imp {
#[template_callback] #[template_callback]
fn on_row_activated(&self, position: u32) { fn on_row_activated(&self, position: u32) {
self.playbin self.obj().window().play_index(position as i64);
.borrow()
.as_ref()
.unwrap()
.play_entry(position as usize);
} }
} }
@ -115,27 +113,6 @@ glib::wrapper! {
} }
impl PlayQueue { impl PlayQueue {
pub fn set_playbin(&self, playbin: &Rc<Playbin>) {
assert!(self
.imp()
.playbin
.replace(Some(Rc::clone(playbin)))
.is_none()); // only set once
playbin
.stopped()
.connect_object(self, |_playbin, play_queue, ()| {
play_queue.model().unwrap().remove_all();
true
});
playbin
.entry_removed()
.connect_object(self, |_playbin, play_queue, index| {
play_queue.model().unwrap().remove(index as u32);
true
});
}
fn window(&self) -> crate::ui::Window { fn window(&self) -> crate::ui::Window {
self.root().unwrap().dynamic_cast().unwrap() self.root().unwrap().dynamic_cast().unwrap()
} }

View file

@ -8,6 +8,9 @@ mod imp {
#[template(resource = "/eu/callcc/audrey/play_queue_song.ui")] #[template(resource = "/eu/callcc/audrey/play_queue_song.ui")]
#[properties(wrapper_type = super::Song)] #[properties(wrapper_type = super::Song)]
pub struct Song { pub struct Song {
#[property(get, set = Self::set_playlist_pos)]
playlist_pos: Cell<i64>,
#[property(set, get)] #[property(set, get)]
draggable: Cell<bool>, draggable: Cell<bool>,
#[property(set, get)] #[property(set, get)]
@ -57,11 +60,13 @@ mod imp {
#[weak(rename_to = self_)] #[weak(rename_to = self_)]
self, self,
move |_, _, _| { move |_, _, _| {
/*
self_ self_
.obj() .obj()
.window() .window()
.playbin() .playbin()
.remove_entry(self_.obj().displayed_position() as usize - 1) .remove_entry(self_.obj().displayed_position() as usize - 1)*/
todo!()
} }
)) ))
.build(); .build();
@ -121,7 +126,7 @@ mod imp {
fn on_drag_begin(&self, drag: &gdk::Drag) { fn on_drag_begin(&self, drag: &gdk::Drag) {
let drag_widget = gtk::ListBox::new(); let drag_widget = gtk::ListBox::new();
let drag_row = super::Song::new(None); let drag_row: super::Song = glib::Object::new();
drag_row.set_draggable(false); drag_row.set_draggable(false);
drag_row.set_show_position(self.obj().show_position()); drag_row.set_show_position(self.obj().show_position());
drag_row.set_show_artist(self.obj().show_artist()); drag_row.set_show_artist(self.obj().show_artist());
@ -160,10 +165,18 @@ mod imp {
true */ true */
false false
} }
fn set_playlist_pos(&self, playlist_pos: i64) {
self.playlist_pos.set(playlist_pos);
self.obj()
.set_current(playlist_pos == self.position.get() as i64);
}
} }
} }
use crate::PlaybinSong;
use adw::prelude::*; use adw::prelude::*;
use adw::subclass::prelude::*;
use glib::Object; use glib::Object;
use gtk::glib; use gtk::glib;
@ -174,40 +187,21 @@ glib::wrapper! {
} }
impl Song { impl Song {
pub fn new(window: Option<&crate::ui::Window>) -> Self {
let song: Self = Object::new();
if let Some(window) = window {
use crate::Event;
crate::event::spawn_object_listener(
window.receiver(),
&song,
|song, event| match event {
Event::PlaybinCurrentEntryChanged => {
song.set_current(
song.window().playbin().current_entry()
== Some(song.position() as usize),
);
}
_ => {}
},
);
}
song
}
fn window(&self) -> crate::ui::Window { fn window(&self) -> crate::ui::Window {
self.root().unwrap().dynamic_cast().unwrap() self.root().unwrap().dynamic_cast().unwrap()
} }
pub fn bind(&self, position: u32, window: &crate::ui::Window) { pub fn bind(&self, position: u32, window: &crate::ui::Window) {
let song: PlaybinSong = window
.playlist_model()
.item(position)
.unwrap()
.dynamic_cast()
.unwrap();
self.set_displayed_position(position + 1); self.set_displayed_position(position + 1);
self.set_song(&window.playbin().entries()[position as usize]); self.set_song(&song);
self.set_current(window.playbin().current_entry() == Some(position as usize));
self.set_position(position); self.set_position(position);
self.set_playlist_pos(window.playlist_pos());
} }
pub fn unbind(&self) {} pub fn unbind(&self) {}

View file

@ -19,9 +19,12 @@ mod imp {
show_cover_art: Cell<bool>, show_cover_art: Cell<bool>,
#[property(get, set)] #[property(get, set)]
volume: Cell<i32>, _volume: Cell<i64>,
#[property(get, set)] #[property(get, set)]
mute: Cell<bool>, _mute: Cell<bool>,
#[property(get, set)]
_pause: Cell<bool>,
#[property(get, set)] #[property(get, set)]
position: Cell<f64>, position: Cell<f64>,
#[property(get, set)] #[property(get, set)]
@ -157,6 +160,10 @@ mod imp {
fn on_mute_toggle(&self) { fn on_mute_toggle(&self) {
self.obj().set_mute(!self.obj().mute()); self.obj().set_mute(!self.obj().mute());
} }
fn window(&self) -> crate::ui::Window {
self.obj().root().unwrap().dynamic_cast().unwrap()
}
} }
impl Drop for Playbar { impl Drop for Playbar {

View file

@ -1,9 +1,10 @@
mod imp { mod imp {
use crate::{Playbin, PlaybinSong}; use crate::mpv;
use crate::PlaybinSong;
use adw::prelude::*; use adw::prelude::*;
use adw::subclass::prelude::*; use adw::subclass::prelude::*;
use glib::subclass::InitializingObject; use glib::subclass::InitializingObject;
use gtk::{gdk, glib}; use gtk::{gdk, gio, glib};
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::rc::Rc; use std::rc::Rc;
use tracing::{event, Level}; use tracing::{event, Level};
@ -29,16 +30,38 @@ mod imp {
pub(super) setup: crate::ui::Setup, pub(super) setup: crate::ui::Setup,
pub(super) playbin: Rc<Playbin>,
pub(super) api: RefCell<Option<Rc<crate::subsonic::Client>>>, pub(super) api: RefCell<Option<Rc<crate::subsonic::Client>>>,
pub(super) _sender: async_broadcast::Sender<crate::Event>, pub(super) mpv: mpv::Handle,
pub(super) inactive_receiver: async_broadcast::InactiveReceiver<crate::Event>, #[property(get)]
playlist_model: gio::ListStore,
#[property(type = i64, get = Self::volume, set = Self::set_volume)]
_volume: (),
#[property(type = bool, get = Self::mute, set = Self::set_mute)]
_mute: (),
#[property(type = bool, get = Self::pause, set = Self::set_pause)]
_pause: (),
#[property(type = i64, get = Self::playlist_pos)]
_playlist_pos: (),
} }
impl Default for Window { impl Default for Window {
fn default() -> Self { fn default() -> Self {
let (sender, receiver) = async_broadcast::broadcast(100); // TODO: constantize let mpv = mpv::Handle::new();
mpv.set_property("audio-client-name", "audrey").unwrap();
mpv.set_property("user-agent", crate::USER_AGENT).unwrap();
mpv.set_property("video", false).unwrap();
mpv.set_property("prefetch-playlist", true).unwrap();
mpv.set_property("gapless-audio", true).unwrap();
mpv.observe_property(0, "volume").unwrap();
mpv.observe_property(1, "mute").unwrap();
mpv.observe_property(2, "pause").unwrap();
mpv.observe_property(3, "playlist-pos").unwrap();
// "Useful to drain property changes before a new file is loaded."
mpv.add_hook(0, "on_before_start_file", 0).unwrap();
Self { Self {
playbar: Default::default(), playbar: Default::default(),
@ -47,10 +70,14 @@ mod imp {
playing_cover_art: Default::default(), playing_cover_art: Default::default(),
song: Default::default(), song: Default::default(),
setup: Default::default(), setup: Default::default(),
playbin: Rc::new(Playbin::new(sender.clone())),
api: Default::default(), api: Default::default(),
_sender: sender, mpv,
inactive_receiver: receiver.deactivate(), playlist_model: gio::ListStore::new::<PlaybinSong>(),
_volume: (),
_mute: (),
_pause: (),
_playlist_pos: (),
} }
} }
} }
@ -76,52 +103,56 @@ mod imp {
fn constructed(&self) { fn constructed(&self) {
self.parent_constructed(); self.parent_constructed();
crate::event::spawn_object_listener( let window = self.obj().downgrade();
self.inactive_receiver.activate_cloned(), glib::spawn_future_local(async move {
self.obj().as_ref(), while let Some(window) = window.upgrade() {
|window, event| { let listener = window.imp().mpv.wakeup_listener();
use crate::Event; while let Some(event) = window.imp().mpv.wait_event(0.0) {
match dbg!(event) { use crate::mpv::Event;
Event::PlaybinVolumeChanged => {
window match event {
.imp() Event::PropertyChange(event) => match event.reply_userdata {
.playbar 0 => {
.set_volume(window.playbin().volume() as i32); assert_eq!(event.name, "volume");
window.notify("volume");
} }
Event::PlaybinMutedChanged => { 1 => {
window.imp().playbar.set_mute(window.playbin().muted()); assert_eq!(event.name, "mute");
window.notify("mute");
} }
Event::PlaybinEntryInserted(index) => { 2 => {
window assert_eq!(event.name, "pause");
.imp() window.notify("pause");
.play_queue
.model()
.unwrap()
.insert(index as u32, &window.playbin().entries()[index]);
} }
_ => {} 3 => {
assert_eq!(event.name, "playlist-pos");
window.notify("playlist-pos");
} }
_ => unreachable!(),
}, },
);
let playbin = Rc::downgrade(&self.playbin); mpv::Event::Hook(event) => match event.reply_userdata {
glib::spawn_future_local(glib::clone!(async move { 0 => {
loop { assert_eq!(&event.name, "on_before_start_file");
match playbin.upgrade() { event!(Level::DEBUG, "on_before_start_file triggered");
None => break, // just use this as a barrier
Some(playbin) => { window.imp().mpv.continue_hook(event.id).unwrap();
let listener = playbin.tick(); }
drop(playbin);
_ => unreachable!(),
},
_ => event!(Level::DEBUG, "unhandled {event:?}"),
}
}
drop(window);
listener.await; listener.await;
} }
} });
}
}));
self.play_queue.set_playbin(&self.playbin);
// set up mpris // set up mpris
let window = self.obj().clone(); let window = self.obj().clone();
@ -147,9 +178,11 @@ mod imp {
crate::Mpris::setup(conn.object_server(), &window) crate::Mpris::setup(conn.object_server(), &window)
.await .await
.expect("could not serve mpris"); .expect("could not serve mpris");
/*
crate::mpris::Player::setup(conn.object_server(), &window.imp().playbin) crate::mpris::Player::setup(conn.object_server(), &window.imp().playbin)
.await .await
.expect("could not serve mpris player"); .expect("could not serve mpris player");
*/
drop(window); // don't keep this alive drop(window); // don't keep this alive
@ -179,11 +212,18 @@ mod imp {
#[template_callback] #[template_callback]
async fn shuffle_all(&self) { async fn shuffle_all(&self) {
self.obj().set_can_click_shuffle_all(false); self.obj().set_can_click_shuffle_all(false);
self.playbin.stop();
self.mpv.command(["stop"]).unwrap();
self.playlist_model.remove_all();
let api = self.api.borrow(); let api = self.api.borrow();
let api = api.as_ref().unwrap(); let api = api.as_ref().unwrap();
for song in api.get_random_songs(10).await.unwrap().into_iter() { for song in api.get_random_songs(10).await.unwrap().into_iter() {
self.playbin.push_entry(PlaybinSong::from_child(api, &song)); let song = PlaybinSong::from_child(api, &song);
self.mpv
.command(["loadfile", &song.stream_url(), "append-play"])
.unwrap();
self.playlist_model.append(&song);
} }
self.obj().set_can_click_shuffle_all(true); self.obj().set_can_click_shuffle_all(true);
} }
@ -221,6 +261,34 @@ mod imp {
*/ */
todo!() todo!()
} }
fn volume(&self) -> i64 {
self.mpv.get_property("volume").unwrap()
}
fn set_volume(&self, volume: i64) {
self.mpv.set_property("volume", volume).unwrap();
}
fn mute(&self) -> bool {
self.mpv.get_property("mute").unwrap()
}
fn set_mute(&self, mute: bool) {
self.mpv.set_property("mute", mute).unwrap();
}
fn pause(&self) -> bool {
self.mpv.get_property("pause").unwrap()
}
fn set_pause(&self, pause: bool) {
self.mpv.set_property("pause", pause).unwrap();
}
fn playlist_pos(&self) -> i64 {
self.mpv.get_property("playlist-pos").unwrap()
}
} }
impl Drop for Window { impl Drop for Window {
@ -233,7 +301,6 @@ mod imp {
use adw::prelude::*; use adw::prelude::*;
use adw::subclass::prelude::*; use adw::subclass::prelude::*;
use gtk::{gio, glib}; use gtk::{gio, glib};
use std::rc::Rc;
glib::wrapper! { glib::wrapper! {
pub struct Window(ObjectSubclass<imp::Window>) pub struct Window(ObjectSubclass<imp::Window>)
@ -246,19 +313,14 @@ impl Window {
let window: Self = glib::Object::builder().property("application", app).build(); let window: Self = glib::Object::builder().property("application", app).build();
// manual bidirectional sync // manual bidirectional sync
/*
window window
.imp() .imp()
.playbar .playbar
.set_volume(window.imp().playbin.volume() as i32); .set_volume(window.imp().mpv.get_property::<i64>("volume") as i32);
window.imp().playbar.connect_notify_local( */
Some("volume"),
glib::clone!(
#[weak(rename_to = playbin)]
window.imp().playbin,
move |playbar, _| playbin.set_volume(playbar.volume() as i64)
),
);
/*
window.imp().playbar.set_mute(window.imp().playbin.muted()); window.imp().playbar.set_mute(window.imp().playbin.muted());
window.imp().playbar.connect_notify_local( window.imp().playbar.connect_notify_local(
Some("mute"), Some("mute"),
@ -276,9 +338,10 @@ impl Window {
playbar.set_duration(entry.duration() as f64); playbar.set_duration(entry.duration() as f64);
true true
}, },
); );*/
// update position every 100 ms // update position every 100 ms
/*
glib::source::timeout_add_local(std::time::Duration::from_millis(100), { glib::source::timeout_add_local(std::time::Duration::from_millis(100), {
let playbar = window.imp().playbar.downgrade(); let playbar = window.imp().playbar.downgrade();
let playbin = Rc::downgrade(&window.imp().playbin); let playbin = Rc::downgrade(&window.imp().playbin);
@ -294,7 +357,7 @@ impl Window {
playbar.set_position(playbin.position().unwrap_or(0.0)); playbar.set_position(playbin.position().unwrap_or(0.0));
glib::ControlFlow::Continue glib::ControlFlow::Continue
} }
}); });*/
window window
.imp() .imp()
@ -302,12 +365,13 @@ impl Window {
.connected() .connected()
.connect_object(&window, |_setup, window, api| { .connect_object(&window, |_setup, window, api| {
window.imp().api.replace(Some(api)); window.imp().api.replace(Some(api));
window.imp().playbin.stop(); //window.imp().playbin.stop();
window.set_can_click_shuffle_all(true); window.set_can_click_shuffle_all(true);
true true
}); });
window.imp().setup.load(); window.imp().setup.load();
/*
window window
.imp() .imp()
.playbin .playbin
@ -317,16 +381,19 @@ impl Window {
.imp() .imp()
.now_playing(&playbin.entries()[playbin.current_entry().unwrap()]); .now_playing(&playbin.entries()[playbin.current_entry().unwrap()]);
true true
}); });*/
window window
} }
pub fn playbin(&self) -> &crate::Playbin { pub fn playbin(&self) -> ! {
&self.imp().playbin todo!()
} }
pub fn receiver(&self) -> async_broadcast::Receiver<crate::Event> { pub fn play_index(&self, index: i64) {
self.imp().inactive_receiver.activate_cloned() self.imp()
.mpv
.command(["playlist-play-index", &index.to_string()])
.unwrap();
} }
} }