Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature]: 作者大佬,落雪linux桌面端有暴漏LRC歌词API吗? #1824

Closed
2 tasks done
MarsSwimmer opened this issue Mar 26, 2024 · 12 comments
Closed
2 tasks done

Comments

@MarsSwimmer
Copy link

解决方案检查

问题描述

最近想开发个落雪音乐Gnome桌面的状态栏歌词,不知道落雪音乐桌面版有没有暴漏相关API?

描述你想要的解决方案

希望落雪linux桌面版也能开放LRC相关的API,

  • 通过127.0.0.1:指定端口 访问
  • 提供当前播放的歌曲、歌词、进度

描述你考虑过的替代方案

No response

附加信息

Exp:YesPlayMusic有暴漏实时歌词的API,包含了当前播放的歌曲的LRC歌词及播放进度,因此只需要简单请求,再按照进度提取就能得到实时歌词,https://github.com/MarsSwimmer/get_lrc

@lyswhut
Copy link
Owner

lyswhut commented Mar 27, 2024

目前没有此类API,你说的通过http服务提供API,具体是怎么样的呢?那第三方软件怎么知道歌曲、歌词、进度等信息的更新?通过SSE?还是websocket?

@MarsSwimmer
Copy link
Author

MarsSwimmer commented Mar 27, 2024

对,是http接口,websocket非必需,

实现逻辑如下:

具体实现

需要落雪音乐桌面app 暴漏一个http接口, 该接口需要提供当前播放歌曲的信息,具体如下:

  • 当前播放歌曲名称;
  • 当前播放歌曲的lrc歌词(带有时间戳的);
  • 当前歌曲的播放进度(歌曲已播放的时间 毫秒) ;

第三方如何使用该接口

流程: 第三方做一个秒级别的调用该接口,解析出实时歌词,并在状态栏展示,
性能问题: 该接口调用属于localhost -> localhost,甚至不需要走网卡,且逻辑简单,所以资源消耗非常低;

为啥第三方不开发这个http接口?

这个http接口需要 播放器提供,因为播放器会控制音乐的播放和切换,第三方难以嵌入逻辑到播放器内部,也难以获取播放进度和控制相关信息。

Websocket接口非必需,如果能提供后续便于扩展krc歌词

通过websocket接口,歌词时效性就变成实时的了,当播放器提供krc歌词时,状态栏就会看到一个字一个字的滚动歌词。

websocket性能解释: 该种情况下,逻辑简单非计算型任务,资源消耗非常低的,且在不打开状态栏歌词时并不会调用该接口。唯一要注意的是播放器作为服务器提供websocket接口时,当连接断开后需要释放资源(有踩过坑),以免造成内存泄漏。

@lyswhut
Copy link
Owner

lyswhut commented Mar 28, 2024

虽然是localhost,但我觉得通过轮询的方式不断调用API还是不够优雅,
因为歌曲切换、歌词更新等状态是播放器产生的,从实时性来说,更好的做法是通过某种方式让播放器主动通知第三方,
通过HTTP的话,我想到的是SSE,它本质是一个长连接的HTTP请求,客户端请求后,服务端会将请求挂起,每当播放器状态改变时,通过流式传输的方式将其返回给客户端,在网上找了个图:
5925c755a8b47a94cc77f45e646d0e44_q-bit-sse-subscription
图片来自https://blog.q-bit.me/how-to-use-nodejs-for-server-sent-events-sse/

SSE的实现参考链接:https://medium.com/deliveryherotechhub/what-is-server-sent-events-sse-and-how-to-implement-it-904938bffd73

这个比websocket更轻量,但是它只能单向通信,从客户端的实现来说,应该就是向服务端发起请求,然后监听请求响应的数据流(正常请求是等请求结束才处理响应的数据)

另一个方案就是用websocket

如果通过某种方式实现了播放器主动通知第三方的功能,那完全可以返回当前句歌词,而不是整个LRC文本,当然还有一种方式是直接将 Gnome桌面的状态栏歌词 功能集成进LX,但这需要编译成可以用nodejs调用的二进制文件

@MarsSwimmer
Copy link
Author

本人不是gnome插件的开发者,偶然间看到yesplaymusic有暴漏歌词相关接口,于是就捣鼓了下实时歌词的命令,然后结合现有的gnome插件(executor周期性调度)展示状态栏实时歌词。也想过开发gnome插件,但门槛有点高,也许以后会开发gnome插件。

粉丝诉求希望能接入lx歌词,为了满足粉丝期待前来叨唠下lx作者,咨询下lrc相关接口;
当下要实现lx状态栏歌词,最快的方案还是接入到现有命令中,通过gnome插件(executor进行秒级调度)。

方案一:http可行
http接口:本人直接集成到现有的命令中,命令的生命周期为:发起请求 -> 解析 -> 给出实时歌词 ->结束,非常简单;

需要提供的接口:

  • 接口一: 提供当前播放歌曲名称,播放进度(单位毫秒),如果能提供实时歌词就更好了,调用起来非常轻量级;
  • 接口二: 全部歌词,建议开放,也许有其它应用场景, 便于linux用户折腾,比如在conky主题里展示全部歌词;

方案二:sse接口,暂时不推荐

  • a) 需要有一个常住内存的客户端接收服务端的歌词通知,稍微有点重,如果走现有方式的调度(现有gnome插件秒级别访问这个常驻内存的客户端),那么这种server-> client的通知没有必要,时效性完全利用不上;

  • b) 如果这个常驻内存的客户端就是gnome插件,那么需要开发gnome状态栏歌词插件,这快暂时没有精力折腾。

方案三:websocket先放一放,
websocket主要用于krc歌词,单个字单个字这种,类似apple music的歌词,但不是所有音乐都有krc歌词,属于锦上添花的功能;且要实现单个字级别的歌词滚动,需要深入到gnome插件,先放一放。

方案四:gnome状态栏歌词插件集成进LX,也先放一放
涉及到gnome插件开发,当前没有精力研究。

综上,http接口的方式实现状态栏歌词是现在最快的方案,若http接口开发成本还行,也能快速上线,还望lx作者可以多考虑下。gnome插件开发这块等有精力再折腾,也许有熟悉gnome插件的大佬,可以提供支持。

@lyswhut
Copy link
Owner

lyswhut commented Mar 29, 2024

大概了解了,那就暂时实现使用普通 HTTP 的方式,数据格式用JSON是否方便?


更新:

c86f36e5-30a7-4f9f-ad33-d33594b2f701

初步安排上了,但是有个问题,播放器处于后台时歌词播放器将被暂停,所以当前句歌词没有更新,已解决


更新:

已经在2.7.0-beta.3加上了,可以去actions下载试试,详细说明看接入文档

对于本地歌曲存在嵌入的封面图片时,封面字段将返回data url的形式,若封面太大,则可能导致响应的数据很大,又不想将封面写到硬盘上返回文件路径,我在想要不要过滤掉此类数据。


更新:

已经在2.7.0-beta.4添加了SSE,用浏览器、postman等请求看看,它只是一个普通的HTTP请求,不需要什么客户端,你可以试试,若对接不了那就用轮询吧:

image

可以在命令行用curl看看输出:curl -N http://127.0.0.1:23330/subscribe-player-status

@MarsSwimmer
Copy link
Author

MarsSwimmer commented Mar 29, 2024

OK,感谢

json数据可以的,解析非常简单;

关于 “播放器处于后台时歌词播放器将被暂停,所以当前句歌词没有更新” 的问题,建议展示最近一行歌词.

接口已对接,现在命令里已增加落雪歌词源,

开放API等大佬上正式版,再次感谢~

@lyswhut
Copy link
Owner

lyswhut commented Mar 30, 2024

关于 “播放器处于后台时歌词播放器将被暂停,所以当前句歌词没有更新” 的问题,建议展示最近一行歌词.

这个已经解决了,忘了划掉

我没用过go,对gnome也不熟悉,我大概看了下,整个流程似乎是使用go写一个命令行的程序,然后给 executor 插件周期性调用,这个插件会显示命令行程序输出的内容到桌面上?那能不能命令行只调用一次,然后持续输出内容呢?如果可以,我觉得可以试试SSE,我问了GPT,go调用SSE似乎也不复杂,就发个请求后循环读取响应的内容而已,试了下以下demo,是可以正常获取数据的,你可以试试运行下面的demo,然后播放、暂停歌曲:

package main

import (
    "fmt"
    "log"
    "net/http"
    "bufio"
)

func listenSSE() {
    client := http.Client{
        Timeout: 0, // No timeout for SSE
    }

    req, err := http.NewRequest("GET", "http://localhost:23330/subscribe-player-status", nil)
    if err != nil {
        log.Fatal("Error creating request:", err)
    }

    req.Header.Set("Accept", "text/event-stream")

    resp, err := client.Do(req)
    if err != nil {
        log.Fatal("Error sending request:", err)
    }
    defer resp.Body.Close()

    // Read SSE events from response body
    scanner := bufio.NewScanner(resp.Body)
    for scanner.Scan() {
        event := scanner.Text()
        fmt.Println("Received event:", event)
    }

    if err := scanner.Err(); err != nil {
        log.Fatal("Error reading response:", err)
    }
}

func main() {
    listenSSE()
}

上面的demo是每次处理一行数据,跳过空行后,event:开头的是事件名行,替换掉这个前缀后剩下的内容去左右空格的字符串就是事件名,data:开头的是该事件所附带的数据行,替换掉这个前缀后剩下的内容去左右空格的字符串,再调用json.Unmarshal解析后的内容就是原始数据,下面是一个调用json.Unmarshal的demo:

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	// 要解码的 JSON 字符串
	jsonString := `"[offset:0]\n[00:25.156]衬托你的伤心\n[00:28.966]尴尬身份给你慰问\n[00:35.20]"` // 或者 `123`

	// 创建一个变量用于存储解码后的数据
	var data interface{}

	// 解码 JSON 字符串
	err := json.Unmarshal([]byte(jsonString), &data)
	if err != nil {
		fmt.Println("解码 JSON 字符串时发生错误:", err)
		return
	}

	// 根据解码后的数据类型进行处理
	switch v := data.(type) {
	case string:
		fmt.Println("解码后的字符串:", v)
	case float64:
		fmt.Println("解码后的数字:", v)
	default:
		fmt.Println("未知类型")
	}
}

但现在SSE返回的数据似乎太多,而你现在的情况只用到歌词,所以这个接口我再加个入参指定订阅的字段吧


更新:

由于现在返回了进度,你可以加上进度的显示,例如:

  • 01:03/4:27 🎤 记忆是条长线盘旋在天边
  • ⏱ 01:03/4:27 🎤 记忆是条长线盘旋在天边
  • 不格式化时间,直接显示秒:320/162 记忆是条长线盘旋在天边

等等...


更新:

不过仔细想想,长期运行的命令行程序似乎也不太适合你这个场景,用户似乎没办法控制它的停止,所以不行就算了。。。

@MarsSwimmer
Copy link
Author

并不行,executor插件不支持这种持续输出的,

一开始是想开发一个专门做lrc歌词状态栏展示的插件的,由于gnome 插件开发门槛有点高,然后资料还少,最后就放弃开发了。

有熟悉gnome插件开发的同学可以私信我,帮忙指点下,还有些插件年久失修,想升级下

@cloud-mist
Copy link

谢谢作者,waybar上终于可以放歌词了。

2024-03-31.00-24-41.mp4

@laoshuikaixue
Copy link

关于 “播放器处于后台时歌词播放器将被暂停,所以当前句歌词没有更新” 的问题,建议展示最近一行歌词.

这个已经解决了,忘了划掉

我没用过go,对gnome也不熟悉,我大概看了下,整个流程似乎是使用go写一个命令行的程序,然后给 executor 插件周期性调用,这个插件会显示命令行程序输出的内容到桌面上?那能不能命令行只调用一次,然后持续输出内容呢?如果可以,我觉得可以试试SSE,我问了GPT,go调用SSE似乎也不复杂,就发个请求后循环读取响应的内容而已,试了下以下demo,是可以正常获取数据的,你可以试试运行下面的demo,然后播放、暂停歌曲:

package main

import (
    "fmt"
    "log"
    "net/http"
    "bufio"
)

func listenSSE() {
    client := http.Client{
        Timeout: 0, // No timeout for SSE
    }

    req, err := http.NewRequest("GET", "http://localhost:23330/subscribe-player-status", nil)
    if err != nil {
        log.Fatal("Error creating request:", err)
    }

    req.Header.Set("Accept", "text/event-stream")

    resp, err := client.Do(req)
    if err != nil {
        log.Fatal("Error sending request:", err)
    }
    defer resp.Body.Close()

    // Read SSE events from response body
    scanner := bufio.NewScanner(resp.Body)
    for scanner.Scan() {
        event := scanner.Text()
        fmt.Println("Received event:", event)
    }

    if err := scanner.Err(); err != nil {
        log.Fatal("Error reading response:", err)
    }
}

func main() {
    listenSSE()
}

上面的demo是每次处理一行数据,跳过空行后,event:开头的是事件名行,替换掉这个前缀后剩下的内容去左右空格的字符串就是事件名,data:开头的是该事件所附带的数据行,替换掉这个前缀后剩下的内容去左右空格的字符串,再调用json.Unmarshal解析后的内容就是原始数据,下面是一个调用json.Unmarshal的demo:

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	// 要解码的 JSON 字符串
	jsonString := `"[offset:0]\n[00:25.156]衬托你的伤心\n[00:28.966]尴尬身份给你慰问\n[00:35.20]"` // 或者 `123`

	// 创建一个变量用于存储解码后的数据
	var data interface{}

	// 解码 JSON 字符串
	err := json.Unmarshal([]byte(jsonString), &data)
	if err != nil {
		fmt.Println("解码 JSON 字符串时发生错误:", err)
		return
	}

	// 根据解码后的数据类型进行处理
	switch v := data.(type) {
	case string:
		fmt.Println("解码后的字符串:", v)
	case float64:
		fmt.Println("解码后的数字:", v)
	default:
		fmt.Println("未知类型")
	}
}

但现在SSE返回的数据似乎太多,而你现在的情况只用到歌词,所以这个接口我再加个入参指定订阅的字段吧

更新:

由于现在返回了进度,你可以加上进度的显示,例如:

* `01:03/4:27 🎤 记忆是条长线盘旋在天边`

* `⏱ 01:03/4:27 🎤 记忆是条长线盘旋在天边`

* 不格式化时间,直接显示秒:`320/162 记忆是条长线盘旋在天边`

等等...

更新:

不过仔细想想,长期运行的命令行程序似乎也不太适合你这个场景,用户似乎没办法控制它的停止,所以不行就算了。。。

感谢作者开发 话说好像没有歌词翻译的提供

@lyswhut
Copy link
Owner

lyswhut commented Apr 16, 2024

下个版本可以以换行符分割的方式提供扩展歌词,
翻译、罗马音、时间标签相同的歌词(同一个LRC文件中出现多句时间相同的歌词)等在LX歌词播放器中称为扩展歌词

@hyj120309
Copy link

OK,感谢

json数据可以的,解析非常简单;

关于 “播放器处于后台时歌词播放器将被暂停,所以当前句歌词没有更新” 的问题,建议展示最近一行歌词.

接口已对接,现在命令里已增加落雪歌词源,

* 效果如下:
  ![截图 2024-03-30 02-37-15](https://private-user-images.githubusercontent.com/146618222/318116521-553a4ed4-aa02-4756-9998-8dc366ed139d.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTM2Njk4NDksIm5iZiI6MTcxMzY2OTU0OSwicGF0aCI6Ii8xNDY2MTgyMjIvMzE4MTE2NTIxLTU1M2E0ZWQ0LWFhMDItNDc1Ni05OTk4LThkYzM2NmVkMTM5ZC5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwNDIxJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDQyMVQwMzE5MDlaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1kMDA2NjI3YTY4MWI0ZDA5MzBiYWYwMGY0OWUzZTk0ZmQ1OWRjYWU2OTg4YmRjZGNjZDk2MjU2NWIwNzczN2E4JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.LjeHadI8ZMxKN64cS8qvfKVynh31xWzA-NE_1GkLVSs)

* 命令使用教程可参照仓库:https://github.com/MarsSwimmer/get_lrc

开放API等大佬上正式版,再次感谢~

大佬大佬,路过问问,你这waybar咋配置的?好好看!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants