2024-11-03 12:41:02 +00:00
|
|
|
use crate::mpv;
|
2024-11-03 15:11:25 +00:00
|
|
|
use crate::signal::{Signal, SignalEmitter};
|
2024-11-03 12:41:02 +00:00
|
|
|
use event_listener::EventListener;
|
2024-11-03 13:59:54 +00:00
|
|
|
use std::cell::{Ref, RefCell};
|
|
|
|
use url::Url;
|
2024-11-03 12:41:02 +00:00
|
|
|
|
2024-11-03 13:59:54 +00:00
|
|
|
pub trait PlaybinEntry {
|
2024-11-03 15:11:25 +00:00
|
|
|
fn url(&self) -> Url;
|
2024-11-03 13:59:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl PlaybinEntry for Url {
|
2024-11-03 15:11:25 +00:00
|
|
|
fn url(&self) -> Url {
|
|
|
|
self.clone()
|
2024-11-03 13:59:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// E: generic entry type
|
|
|
|
pub struct Playbin<E> {
|
2024-11-03 12:41:02 +00:00
|
|
|
mpv: mpv::Handle,
|
2024-11-03 13:59:54 +00:00
|
|
|
entries: RefCell<Vec<E>>,
|
|
|
|
|
2024-11-03 15:11:25 +00:00
|
|
|
volume_changed: SignalEmitter<Self, ()>,
|
|
|
|
muted_changed: SignalEmitter<Self, ()>,
|
|
|
|
paused_changed: SignalEmitter<Self, ()>,
|
|
|
|
current_entry_changed: SignalEmitter<Self, ()>,
|
2024-11-03 13:59:54 +00:00
|
|
|
|
2024-11-03 17:01:29 +00:00
|
|
|
entry_inserted: SignalEmitter<Self, usize>,
|
2024-11-03 15:11:25 +00:00
|
|
|
stopped: SignalEmitter<Self, ()>,
|
2024-11-03 17:01:29 +00:00
|
|
|
entry_removed: SignalEmitter<Self, usize>,
|
2024-11-03 15:11:25 +00:00
|
|
|
|
|
|
|
file_started: SignalEmitter<Self, ()>,
|
2024-11-03 12:41:02 +00:00
|
|
|
}
|
|
|
|
|
2024-11-03 13:59:54 +00:00
|
|
|
impl<E> Default for Playbin<E> {
|
2024-11-03 12:41:02 +00:00
|
|
|
fn default() -> Self {
|
|
|
|
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();
|
|
|
|
|
2024-11-03 15:11:25 +00:00
|
|
|
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();
|
2024-11-03 13:59:54 +00:00
|
|
|
|
|
|
|
// "Useful to drain property changes before a new file is loaded."
|
|
|
|
mpv.add_hook(0, "on_before_start_file", 0).unwrap();
|
|
|
|
|
|
|
|
Self {
|
|
|
|
mpv,
|
|
|
|
entries: RefCell::new(vec![]),
|
|
|
|
|
2024-11-03 15:11:25 +00:00
|
|
|
volume_changed: Default::default(),
|
|
|
|
muted_changed: Default::default(),
|
2024-11-03 13:59:54 +00:00
|
|
|
paused_changed: Default::default(),
|
|
|
|
current_entry_changed: Default::default(),
|
2024-11-03 12:41:02 +00:00
|
|
|
|
2024-11-03 13:59:54 +00:00
|
|
|
entry_inserted: Default::default(),
|
|
|
|
stopped: Default::default(),
|
|
|
|
entry_removed: Default::default(),
|
2024-11-03 15:11:25 +00:00
|
|
|
|
|
|
|
file_started: Default::default(),
|
2024-11-03 13:59:54 +00:00
|
|
|
}
|
2024-11-03 12:41:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-03 13:59:54 +00:00
|
|
|
impl<E> Playbin<E>
|
|
|
|
where
|
|
|
|
E: PlaybinEntry,
|
|
|
|
{
|
2024-11-03 15:11:25 +00:00
|
|
|
pub fn volume(&self) -> i64 {
|
|
|
|
self.mpv.get_property("volume").unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_volume(&self, volume: i64) {
|
|
|
|
self.mpv.set_property("volume", volume).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn muted(&self) -> bool {
|
|
|
|
self.mpv.get_property("mute").unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_muted(&self, muted: bool) {
|
|
|
|
self.mpv.set_property("mute", muted).unwrap();
|
|
|
|
}
|
|
|
|
|
2024-11-03 13:59:54 +00:00
|
|
|
pub fn paused(&self) -> bool {
|
|
|
|
self.mpv.get_property("pause").unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_paused(&self, paused: bool) {
|
|
|
|
self.mpv.set_property("pause", paused).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn position(&self) -> Option<f64> {
|
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
|
2024-11-03 17:01:29 +00:00
|
|
|
pub fn current_entry(&self) -> Option<usize> {
|
2024-11-03 13:59:54 +00:00
|
|
|
self.mpv
|
|
|
|
.get_property::<i64>("playlist-pos")
|
|
|
|
.unwrap()
|
|
|
|
.try_into()
|
|
|
|
.ok()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn seek(&self, _position: f64) {
|
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn next_entry(&self) {
|
|
|
|
self.mpv.command(["playlist-next"]).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn prev_entry(&self) {
|
|
|
|
self.mpv.command(["playlist-prev"]).unwrap();
|
|
|
|
}
|
|
|
|
|
2024-11-03 17:01:29 +00:00
|
|
|
pub fn play_entry(&self, index: usize) {
|
2024-11-03 13:59:54 +00:00
|
|
|
self.mpv
|
|
|
|
.command(["playlist-play-index", &index.to_string()])
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn entries(&self) -> Ref<'_, [E]> {
|
|
|
|
Ref::map(self.entries.borrow(), Vec::as_ref)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn push_entry(&self, entry: E) {
|
|
|
|
let mut entries = self.entries.borrow_mut();
|
|
|
|
self.mpv
|
|
|
|
.command(["loadfile", entry.url().as_str(), "append-play"])
|
|
|
|
.unwrap();
|
|
|
|
let index = entries.len();
|
|
|
|
entries.push(entry);
|
|
|
|
|
|
|
|
drop(entries);
|
2024-11-03 17:01:29 +00:00
|
|
|
self.entry_inserted.emit(self, index as usize);
|
2024-11-03 13:59:54 +00:00
|
|
|
}
|
|
|
|
|
2024-11-03 17:01:29 +00:00
|
|
|
pub fn insert_entry(&self, index: usize, entry: E) {
|
2024-11-03 13:59:54 +00:00
|
|
|
let mut entries = self.entries.borrow_mut();
|
|
|
|
self.mpv
|
|
|
|
.command(["loadfile", entry.url().as_str(), "insert-at-play"])
|
|
|
|
.unwrap();
|
|
|
|
entries.insert(index as usize, entry);
|
|
|
|
|
|
|
|
drop(entries);
|
2024-11-03 15:11:25 +00:00
|
|
|
self.entry_inserted.emit(self, index);
|
2024-11-03 13:59:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// stop playback and clear playlist
|
|
|
|
pub fn stop(&self) {
|
|
|
|
let mut entries = self.entries.borrow_mut();
|
|
|
|
self.mpv.command(["stop"]).unwrap();
|
|
|
|
entries.clear();
|
|
|
|
|
|
|
|
drop(entries);
|
2024-11-03 15:11:25 +00:00
|
|
|
self.stopped.emit(self, ());
|
2024-11-03 13:59:54 +00:00
|
|
|
}
|
|
|
|
|
2024-11-03 17:01:29 +00:00
|
|
|
pub fn remove_entry(&self, index: usize) {
|
2024-11-03 13:59:54 +00:00
|
|
|
let mut entries = self.entries.borrow_mut();
|
2024-11-03 17:45:52 +00:00
|
|
|
self.mpv
|
|
|
|
.command(["playlist-remove", &index.to_string()])
|
|
|
|
.unwrap();
|
2024-11-03 13:59:54 +00:00
|
|
|
entries.remove(index as usize);
|
|
|
|
|
|
|
|
drop(entries);
|
2024-11-03 15:11:25 +00:00
|
|
|
self.entry_removed.emit(self, index);
|
2024-11-03 13:59:54 +00:00
|
|
|
}
|
|
|
|
|
2024-11-03 17:01:29 +00:00
|
|
|
pub fn move_entry(&self, _from: usize, _to: usize) {
|
2024-11-03 13:59:54 +00:00
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
|
2024-11-03 12:41:02 +00:00
|
|
|
pub fn tick(&self) -> EventListener {
|
|
|
|
let listener = self.mpv.wakeup_listener();
|
|
|
|
while let Some(event) = self.mpv.wait_event(0.0) {
|
|
|
|
self.handle_event(event);
|
|
|
|
}
|
|
|
|
listener
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_event(&self, event: mpv::Event) {
|
2024-11-03 13:59:54 +00:00
|
|
|
match event {
|
|
|
|
mpv::Event::PropertyChange(event) => match event.reply_userdata {
|
|
|
|
0 => {
|
2024-11-03 15:11:25 +00:00
|
|
|
assert_eq!(&event.name, "volume");
|
|
|
|
self.volume_changed.emit(self, ());
|
|
|
|
println!("new volume! {:?}", self.volume());
|
|
|
|
}
|
|
|
|
|
|
|
|
1 => {
|
|
|
|
assert_eq!(&event.name, "mute");
|
|
|
|
self.muted_changed.emit(self, ());
|
|
|
|
println!("new muted! {:?}", self.muted());
|
|
|
|
}
|
|
|
|
|
|
|
|
2 => {
|
2024-11-03 13:59:54 +00:00
|
|
|
assert_eq!(&event.name, "pause");
|
2024-11-03 15:11:25 +00:00
|
|
|
self.paused_changed.emit(self, ());
|
2024-11-03 13:59:54 +00:00
|
|
|
println!("new paused! {:?}", self.paused());
|
|
|
|
}
|
|
|
|
|
2024-11-03 15:11:25 +00:00
|
|
|
3 => {
|
2024-11-03 13:59:54 +00:00
|
|
|
assert_eq!(&event.name, "playlist-pos");
|
2024-11-03 15:11:25 +00:00
|
|
|
self.current_entry_changed.emit(self, ());
|
2024-11-03 13:59:54 +00:00
|
|
|
println!("new current_entry! {:?}", self.current_entry());
|
|
|
|
}
|
|
|
|
|
|
|
|
_ => unreachable!(),
|
|
|
|
},
|
|
|
|
|
|
|
|
mpv::Event::Hook(event) => match event.reply_userdata {
|
|
|
|
0 => {
|
|
|
|
assert_eq!(&event.name, "on_before_start_file");
|
|
|
|
// just use this as a barrier
|
|
|
|
println!("on_before_start_file triggered");
|
2024-11-03 14:06:11 +00:00
|
|
|
self.mpv.continue_hook(event.id).unwrap();
|
2024-11-03 13:59:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_ => unreachable!(),
|
|
|
|
},
|
|
|
|
|
2024-11-03 15:11:25 +00:00
|
|
|
mpv::Event::StartFile(_) => {
|
|
|
|
// since we set up the hook before, the current song is guaranteed not to change
|
|
|
|
// under our feet
|
|
|
|
self.file_started.emit(self, ());
|
|
|
|
}
|
|
|
|
|
2024-11-03 13:59:54 +00:00
|
|
|
_ => println!("mpv event {:?}", event),
|
|
|
|
}
|
2024-11-03 12:41:02 +00:00
|
|
|
}
|
2024-11-03 15:11:25 +00:00
|
|
|
|
|
|
|
pub fn volume_changed(&self) -> Signal<'_, Self, ()> {
|
|
|
|
self.volume_changed.signal()
|
|
|
|
}
|
|
|
|
|
2024-11-03 15:17:54 +00:00
|
|
|
pub fn muted_changed(&self) -> Signal<'_, Self, ()> {
|
|
|
|
self.muted_changed.signal()
|
|
|
|
}
|
|
|
|
|
2024-11-03 17:45:52 +00:00
|
|
|
pub fn current_entry_changed(&self) -> Signal<'_, Self, ()> {
|
|
|
|
self.current_entry_changed.signal()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn entry_inserted(&self) -> Signal<'_, Self, usize> {
|
|
|
|
self.entry_inserted.signal()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn stopped(&self) -> Signal<'_, Self, ()> {
|
|
|
|
self.stopped.signal()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn entry_removed(&self) -> Signal<'_, Self, usize> {
|
|
|
|
self.entry_removed.signal()
|
|
|
|
}
|
|
|
|
|
2024-11-03 15:11:25 +00:00
|
|
|
pub fn file_started(&self) -> Signal<'_, Self, ()> {
|
|
|
|
self.file_started.signal()
|
|
|
|
}
|
2024-11-03 12:41:02 +00:00
|
|
|
}
|
|
|
|
|
2024-11-03 13:59:54 +00:00
|
|
|
impl<E> Drop for Playbin<E> {
|
2024-11-03 12:41:02 +00:00
|
|
|
fn drop(&mut self) {
|
|
|
|
println!("dropping Playbin2");
|
|
|
|
self.mpv.command(["quit"]).unwrap();
|
|
|
|
}
|
|
|
|
}
|