将网易云音乐网页版歌词显示到 Mac 菜单栏

0. 需求来源

网易云音乐的 Mac 客户端,无法将播放记录同步到 Last.fm, 而且也不太想下载客户端。所以想听歌的时候一般都用网页版,还可以配合 Web Scrobbler 追踪播放记录。 (使用过程中还顺便提交了 两个 PR. )

但是网页版歌词无法在菜单栏上显示,只能在当前网页显示。所以搜了一圈找到一个 SwiftBar 开源工具,可以自定义菜单栏。 (后来发现也可以在播放条右边找到画中画歌词按钮,可以开启一个画中画功能。搜了一下原理大概是不断将歌词绘制到一个 cavans 上,然后新建一个 video 元素,可以不 append 到页面中,直接通过 requestPictureInPicture 打开画中画。此为后话,有兴趣的可以深入了解。)

下面记录一下具体步骤。

1. 安装菜单栏自定义工具 SwiftBar

1
brew install swiftbar

下载慢?试试设置镜像:https://mirrors.tuna.tsinghua.edu.cn/help/homebrew/

2. 启动歌词推送服务器

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
//  main.go 
package main

import (
	"fmt"
	"net/http"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

func main() {
	type Client struct {
		ID string
		Ch chan string
	}
	var (
		nextID   = atomic.Int64{}
		title    = ""         // 
		by       = ""         // 
		clients  = sync.Map{} //  string -> *Client
		actionCh = make(chan string, 1)
	)

	// 
	http.HandleFunc("/action/send", func(w http.ResponseWriter, r *http.Request) {
		var q = r.URL.Query()
		var action = q.Get("action")
		fmt.Printf(">> action = %v\n", action)
		select {
		case actionCh <- action:
		default:
		}
	})

	http.HandleFunc("/action/get", func(w http.ResponseWriter, r *http.Request) {
		var action = "nop"
		select {
		case action = <-actionCh:
		case <-time.After(10 * time.Second): // 10s 
		}
		fmt.Fprintln(w, action)
	})

	// 
	http.HandleFunc("/set", func(w http.ResponseWriter, r *http.Request) {
		var q = r.URL.Query()
		if q.Has("title") { // 
			title = q.Get("title")
			by = q.Get("by")
			fmt.Println()
			fmt.Println()
			fmt.Println(title + " - " + by)
			fmt.Println()
		}
		if q.Has("current") { // 
			var lyrics = r.URL.Query().Get("current")
			lyrics = strings.TrimSpace(lyrics)
			fmt.Println(lyrics)
			clients.Range(func(_, value any) bool { // 
				client := value.(*Client)
				select {
				case client.Ch <- lyrics:
				default: // 
				}
				return true
			})
		}
	})

	// 
	http.HandleFunc("/next", func(w http.ResponseWriter, r *http.Request) {
		var (
			lyrics = ""
			client = &Client{
				ID: fmt.Sprintf("%d", nextID.Add(1)),
				Ch: make(chan string, 1),
			}
		)
		clients.Store(client.ID, client)
		defer func() {
			clients.Delete(client.ID)
		}()

		select {
		case lyrics = <-client.Ch: // 
		case <-time.After(10 * time.Second): // 10s 
		}

		fmt.Fprintln(w, "~~~") //  Swiftbar 
		if lyrics != "" {      // 
			fmt.Fprintln(w, lyrics)
			if title != "" { //  
				fmt.Fprintln(w, "---")
				fmt.Fprintln(w, title)
				fmt.Fprintln(w, by)
			}
		} else { // 
			if title != "" {
				fmt.Fprintln(w, title+" - "+by)
			} else {
				fmt.Fprintln(w, "未在播放")
			}
		}
	})

	fmt.Printf("服务已启动\n")
	http.ListenAndServe(":51917", nil)
}

func log(format string, args ...any) {
	fmt.Printf(format+"\n", args...)
}
1
2
# 然后启动它
go run main.go

3. 安装油猴插件将网页版歌词实时推送

点击 这里 安装。 或者打开 这个页面 再点击右上角的 Raw 也可(这种方式是最新脚本)。 `

4. 打开 SwiftBar 加载插件

先设置好插件目录,然后在插件目录中新建文件 163lyrics.sh, 内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#!/usr/bin/env bash
# <bitbar.title>网易云音乐网页版歌词显示</bitbar.title>
# <bitbar.version>v1.1</bitbar.version>
# <bitbar.author>Youth.霖</bitbar.author>
# <bitbar.author.github>youthlin</bitbar.author.github>
# <bitbar.desc>网易云音乐网页版歌词显示</bitbar.desc>
# <bitbar.abouturl>https://gist.github.com/youthlin/be34fa9bb50b37ac39aa0ce59265632b/</bitbar.abouturl>
# <bitbar.droptypes>Supported UTI's for dropping things on menu bar</bitbar.droptypes>
# <swiftbar.runInBash>false</swiftbar.runInBash>
# <swiftbar.hideRunInTerminal>true</swiftbar.hideRunInTerminal>
# <swiftbar.hideLastUpdated>true</swiftbar.hideLastUpdated>
# <swiftbar.hideDisablePlugin>true</swiftbar.hideDisablePlugin>
# <swiftbar.type>streamable</swiftbar.type>

# 如果带参数 就执行动作 通过menu生成的菜单 点击时触发
if [[ "$1" = "action" ]]; then
    # 发送控制指令
    curl http://localhost:51917/action/send?action=$2 >/dev/null 2>&1
    exit
fi

menu() { # 输出菜单
    echo "上一曲    | terminal=false bash=$0 param0=action param1=prev"
    echo "暂停/播放 | terminal=false bash=$0 param0=action param1=toggle"
    echo "下一曲    | terminal=false bash=$0 param0=action param1=next"
}

refresh() { # 更新歌词
    # 服务器总是以 ~~~ 开头 10s超时
    curl http://localhost:51917/next 2>/dev/null && menu # 输出歌词、曲名、艺术家
    # 如果输出歌词成功 补充菜单
}

fallback() {   # 如果更新歌词失败
    echo '~~~' # 刷新
    echo '歌词推送服务器未启动'
    sleep 1 # 1s后重试(外层调用时死循环)
}

echo '~~~'
echo '播放以显示歌词'
menu

while true; do
    # streamable 表示该脚本会不断输出 遇到 ~~~ 表示刷新
    refresh || fallback
done

效果

打开 https://music.163.com/ 播放,应该就能在菜单栏上看到歌词了。

如果没有登录,想要登录发现播放列表会遮挡登录组件,可以先暂停油猴脚本,因为脚本需要确保歌词界面打开,才能读取到当前歌词。 或者在控制台执行:

1
2
document.querySelector('#g_playlist .listbd').style.height='60px'
document.querySelector('#g_playlist').style.height='100px'

使播放列表高度变矮一些。

效果-菜单栏显示歌词
效果-菜单栏显示歌词

注:代码可以在这里找到
https://gist.github.com/youthlin/be34fa9bb50b37ac39aa0ce59265632b

Reader Echoes

No comments yet

表情

评论提交后需经审核才会显示。