van_core/
lib.rs

1mod mpv;
2
3use anyhow::{anyhow, Result};
4use std::sync::{
5    mpsc::{Receiver, Sender},
6    Arc
7};
8use time::{format_description, UtcOffset};
9
10use cursive::{
11    event::{Event, Key},
12    view::SizeConstraint,
13    views::{
14        Dialog, DummyView, LinearLayout, ResizedView, ScrollView, SelectView, TextContent, TextView,
15    },
16    Cursive, View,
17};
18use log::{error};
19use mpv::{Van, DEFAULT_VOL};
20use mpv::VanControl;
21
22use mpv::PlayStatus;
23
24struct CurrentStatus {
25    vol: Option<Arc<TextContent>>,
26    current_song_status: Arc<TextContent>,
27    current_artist_status: Arc<TextContent>,
28    current_time_status: Arc<TextContent>,
29}
30
31/// Init Cursive view
32/// ```rust
33/// use cursive::{Cursive, CursiveExt};
34///
35///
36/// let mut siv = Cursive::default();
37///
38/// let (control_tx, control_rx) = std::sync::mpsc::channel();
39/// if let Err(e) = van_core::init_siv(&mut siv, vec!["https://www.bilibili.com/video/BV1HB4y1175c".to_string()], control_tx, control_rx) {
40///     eprintln!("{}", e);
41///     std::process::exit(1);
42/// }
43///
44/// siv.run();
45/// ```
46pub fn init_siv(
47    siv: &mut Cursive,
48    args: Vec<String>,
49    control_tx: Sender<VanControl>,
50    control_rx: Receiver<VanControl>,
51) -> Result<()> {
52    let (view, current_status) = get_view();
53    let vol_status = current_status.vol.unwrap();
54    let van = Arc::new(Van::new()?);
55    let van_clone = van.clone();
56    let van_clone_2 = van.clone();
57
58    for i in args {
59        van.add(&i)?;
60    }
61
62    start_mpv(
63        CurrentStatus {
64            vol: None,
65            ..current_status
66        },
67        control_rx,
68        van_clone
69    );
70
71    set_cursive(vol_status, control_tx, siv, van_clone_2);
72
73    siv.add_layer(view);
74
75    Ok(())
76}
77
78/// Destroy mpv
79pub fn destroy_mpv(control_tx: Sender<VanControl>) -> Result<()> {
80    control_tx.send(VanControl::Exit)?;
81
82    Ok(())
83}
84
85fn set_cursive(vol_status: Arc<TextContent>, control_tx: Sender<VanControl>, siv: &mut Cursive, van: Arc<Van>) {
86    let volume_status_clone = vol_status.clone();
87    let control_tx_clone = control_tx.clone();
88    let control_tx_clone_2 = control_tx.clone();
89    let control_tx_clone_3 = control_tx.clone();
90    let control_tx_clone_4 = control_tx.clone();
91    let van_clone = van.clone();
92    let van_clone_2 = van.clone();
93
94    siv.add_global_callback('=', move |_| {
95        if let Err(e) = add_volume(control_tx.clone(), volume_status_clone.clone(), van_clone.clone()) {
96            error!("{}", e);
97        }
98    });
99    siv.add_global_callback('-', move |_| {
100        if let Err(e) = reduce_volume(control_tx_clone_2.clone(), vol_status.clone(), van_clone_2.clone()) {
101            error!("{}", e);
102        }
103    });
104    siv.add_global_callback(Event::Key(Key::Right), move |_| {
105        control_tx_clone_3.send(VanControl::NextSong).unwrap();
106    });
107    siv.add_global_callback(Event::Key(Key::Left), move |_| {
108        control_tx_clone.send(VanControl::PrevSong).unwrap();
109    });
110    siv.add_global_callback('p', move |_| {
111        control_tx_clone_4.send(VanControl::PauseControl).unwrap();
112    });
113    siv.add_global_callback('l', move |s| {
114        playlist_view(s, van.clone());
115    });
116    siv.add_global_callback('~', cursive::Cursive::toggle_debug_console);
117    siv.set_autorefresh(true);
118}
119
120fn get_view() -> (Dialog, CurrentStatus) {
121    let mut vol_view = TextView::new(format!("vol: {}", DEFAULT_VOL));
122    let vol_status = Arc::new(vol_view.get_shared_content());
123
124    let mut current_song_view = TextView::new("Unknown");
125    let current_song_status = Arc::new(current_song_view.get_shared_content());
126
127    let mut current_time_view = TextView::new("-/-");
128    let current_time_status = Arc::new(current_time_view.get_shared_content());
129
130    let mut current_artist_view = TextView::new("Unknown");
131    let current_artist_status = Arc::new(current_artist_view.get_shared_content());
132
133    let view = wrap_in_dialog(
134        LinearLayout::vertical()
135            .child(current_song_view.center())
136            .child(DummyView {})
137            .child(current_artist_view.center())
138            .child(DummyView {})
139            .child(current_time_view.center())
140            .child(DummyView {})
141            .child(vol_view.center()),
142        "Van",
143        None,
144    );
145
146    (
147        view,
148        CurrentStatus {
149            vol: Some(vol_status),
150            current_song_status,
151            current_artist_status,
152            current_time_status,
153        },
154    )
155}
156
157fn playlist_view(siv: &mut Cursive, van: Arc<Van>) {
158    let playlist = van.get_playlist();
159    let mut files = vec![];
160    if let Ok(playlist) = playlist {
161        for i in playlist {
162            files.push(i.filename);
163        }
164    } else {
165        error!("{:?}", playlist.unwrap_err());
166    }
167    let view = wrap_in_dialog(
168        SelectView::new()
169            .with_all_str(files.clone())
170            .on_submit(move |s, c: &String| {
171                let index = files.clone().iter().position(|x| x == c);
172                if let Some(index) = index {
173                    van.force_play(index.try_into().unwrap()).ok();
174                }
175                s.pop_layer();
176            }),
177        "Playlist",
178        None,
179    )
180    .button("Back", |s| {
181        s.cb_sink()
182            .send(Box::new(|s| {
183                s.pop_layer();
184            }))
185            .unwrap();
186    });
187    siv.add_layer(view);
188}
189
190fn start_mpv(current_status: CurrentStatus, control_rx: Receiver<VanControl>, van: Arc<Van>) {
191    let van_clone = van.clone();
192    std::thread::spawn(move || {
193        let (getinfo_tx, getinfo_rx) = std::sync::mpsc::channel();
194        let current_song_status_clone = current_status.current_song_status.clone();
195        std::thread::spawn(move || {
196            // FIXME: Non-C locale detected. This is not supported.
197            // Call 'setlocale(LC_NUMERIC, "C");' in your code.
198            let buf = std::ffi::CString::new("C").expect("Unknown Error!");
199            unsafe { libc::setlocale(libc::LC_NUMERIC, buf.as_ptr()) };
200            if let Err(e) = van.play(control_rx, getinfo_tx) {
201                eprintln!("{}", e);
202                std::process::exit(1);
203            }
204        });
205        loop {
206            let mut time_str = String::from("-/-");
207            let r = getinfo_rx.try_recv();
208            if let Ok(status) = r {
209                // info!("Recviver! {:?}", status);
210                match status {
211                    PlayStatus::MediaInfo(m) => {
212                        current_song_status_clone.set_content(m.title);
213                        current_status.current_artist_status.set_content(m.artist);
214                        if let Ok(current_time) = get_time(m.current_time) {
215                            time_str = time_str.replace("-/", &format!("{}/", current_time));
216                        }
217                        if let Ok(duration) = get_time(m.duration) {
218                            time_str = time_str.replace("/-", &format!("/{}", duration));
219                        }
220                        current_status.current_time_status.set_content(time_str);
221                    }
222                    PlayStatus::Loading => {
223                        if let Ok(name) = van_clone.get_file_name() {
224                            current_status.current_song_status.clone().set_content(name);
225                            current_status
226                                .current_artist_status
227                                .clone()
228                                .set_content("Unknown");
229                            current_status
230                                .current_time_status
231                                .clone()
232                                .set_content("-/-");
233                        }
234                    }
235                }
236            }
237        }
238    });
239}
240
241fn add_volume(control_tx: Sender<VanControl>, vol_status: Arc<TextContent>, van: Arc<Van>) -> Result<()> {
242    let mut current_vol = van.get_volume()?;
243    if current_vol < 100.0 {
244        current_vol += 5.0;
245        control_tx.send(VanControl::SetVolume(current_vol))?;
246        vol_status.set_content(format!("vol: {}", current_vol));
247    }
248
249    Ok(())
250}
251
252fn reduce_volume(control_tx: Sender<VanControl>, vol_status: Arc<TextContent>, van: Arc<Van>) -> Result<()> {
253    let mut current_vol = van.get_volume()?;
254    if current_vol > 0.0 {
255        current_vol -= 5.0;
256        control_tx.send(VanControl::SetVolume(current_vol))?;
257        vol_status.set_content(format!("vol: {}", current_vol));
258    }
259
260    Ok(())
261}
262
263fn wrap_in_dialog<V: View, S: Into<String>>(inner: V, title: S, width: Option<usize>) -> Dialog {
264    Dialog::around(ResizedView::new(
265        SizeConstraint::AtMost(width.unwrap_or(64)),
266        SizeConstraint::Free,
267        ScrollView::new(inner),
268    ))
269    .padding_lrtb(2, 2, 1, 1)
270    .title(title)
271}
272
273fn get_time(time: i64) -> Result<String> {
274    let f = format_description::parse("[offset_minute]:[offset_second]")?;
275    let offset = UtcOffset::from_whole_seconds(time.try_into()?)?;
276    let minute = offset.whole_minutes();
277    let date = offset.format(&f)?;
278    let sess = date
279        .split_once(':')
280        .map(|x| x.1)
281        .ok_or_else(|| anyhow!("Can not convert time!"))?;
282    let date = format!("{}:{}", minute, sess);
283
284    Ok(date)
285}