Audlyrics
动机⌗
偶然发现一直在用的音乐播放器 Audacious 附带了一个命令行工具 audtool
,可以控制播放和获取播放信息。
这东西好啊,一直以来在 Plasmashell 上显示歌词的执念终于可以满足了。
技术路线⌗
说起 Plasmashell 当然得是 Plamoid ,写 Plasmoid 当然得用 QML 。 但是吧, QML 框架好像不能直接访问文件系统,也不能执行命令,而要做到这些就得用 C++ ,这就很头疼了。
所以思索之下,我最终选择了用 Rust 写一个服务器,然后 QML 只负责展示。
Rust 的服务器框架随便选了个 Hyper 。
实现⌗
思路很简单,在服务端开子进程执行 audtool
,获取当前曲目位置和播放时间,刚好我的歌词就存在曲目边上,和音频文件同名。
从歌词文件里读出来 LRC 格式的文本,然后解析后存起来,前端轮询的时候根据播放时间返回当前句子就行了。
QML 懒得学,但是刚好看见过一个叫 ypm-lyrics
的 Plasmoid ,这个是用来显示网易云第三方客户端 YesplayMusic 的歌词的,就直接拿来用了。
在和 Rust 的异步生命周期检查搏斗许久之后,终于搞定了服务端。
从中总结出来的经验是:闭包写短点,要不然容易晕。。。(汗
然后为了无感启动和关闭服务端,又为了不想让服务端一直开着(虽然基本不会占资源),在启动服务端时会把 Audacious 作为子进程启动,然后守护 Audacious 退出。
let mut child = Command::new("audacious").spawn()
.expect("Cannot start audacious. Is it Installed?");
let state = Arc::new(Mutex::new(State::new()));
let make_service = make_service_fn(move |_: &AddrStream| {
let state = state.clone();
let service = service_fn(
move |req: Request<Body>| {
handle(state.clone(), req)
});
async move {
Ok::<_, Infallible>(service)
}
});
let addr = ([127, 0, 0, 1], 30123).into();
let server = Server::bind(&addr).serve(make_service);
let grace = server.with_graceful_shutdown(async move {
match child.wait().await {
Ok(code) => println!("Audacious returned with {}", code),
Err(e) => println!("Audacious returned with IoError {:?}", e)
};
});
if let Err(e) = grace.await {
eprintln!("Server error: {}", e);
}
端口直接写死了。
效果⌗
源代码可以在 Github 找到。
Read other posts