mirror of
https://github.com/AynaLivePlayer/AynaLivePlayer.git
synced 2025-12-10 12:18:13 +08:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e305e0da6e | ||
|
|
666ca2a16e | ||
|
|
bb24c54d46 | ||
|
|
5ea45858f7 | ||
|
|
90e5c8a9bf | ||
|
|
db2030b3c0 | ||
|
|
05c44079e2 | ||
|
|
f6b58a3e9d | ||
|
|
a385b341f7 | ||
|
|
f69327d53d | ||
|
|
313e48cf21 | ||
|
|
2b7151e1d2 | ||
|
|
24457e0acd | ||
|
|
8f2b975455 | ||
|
|
9e7b062790 | ||
|
|
8602e6470a | ||
|
|
41e2a4775a | ||
|
|
6f5cfc9028 | ||
|
|
f47cc14151 | ||
|
|
78cf40ff26 | ||
|
|
d90311acfe | ||
|
|
94615265bf | ||
|
|
6e4c78daf2 | ||
|
|
632f531cdb | ||
|
|
7bf9372898 | ||
|
|
fd91b1e130 | ||
|
|
3431b5cafe | ||
|
|
3907ff96d5 | ||
|
|
dfce89f96e | ||
|
|
dc3ab46ad0 | ||
|
|
6bdb0acf93 | ||
|
|
6e18df9b41 | ||
|
|
e0849f0d65 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1 +1,3 @@
|
||||
.idea
|
||||
.idea
|
||||
assets/webinfo/*.html
|
||||
assets/webinfo/assets
|
||||
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Aynakeya
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -9,6 +9,10 @@ QQ group: 621035845
|
||||
## build
|
||||
|
||||
```
|
||||
go build -ldflags -H=windowsgui app/gui/main.go
|
||||
|
||||
go build -o AynaLivePlayer.exe -ldflags -H=windowsgui app/gui/main.go
|
||||
```
|
||||
|
||||
## packaging
|
||||
```
|
||||
fyne package --src path_to_gui --exe AynaLivePlayer.exe --appVersion 0.8.4 --icon path_to_icon
|
||||
```
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Printf("BiliAudioBot Revive %s\n", config.VERSION)
|
||||
fmt.Printf("BiliAudioBot Revive %s\n", config.Version)
|
||||
logger.Logger.SetLevel(logrus.DebugLevel)
|
||||
fmt.Println("Please enter room id")
|
||||
var roomid string
|
||||
|
||||
@@ -4,38 +4,26 @@ import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/controller"
|
||||
"AynaLivePlayer/gui"
|
||||
"AynaLivePlayer/i18n"
|
||||
"AynaLivePlayer/logger"
|
||||
"AynaLivePlayer/plugin/diange"
|
||||
"AynaLivePlayer/plugin/qiege"
|
||||
"AynaLivePlayer/plugin/textinfo"
|
||||
"fmt"
|
||||
"github.com/mitchellh/panicwrap"
|
||||
"github.com/sirupsen/logrus"
|
||||
"os"
|
||||
"AynaLivePlayer/plugin/webinfo"
|
||||
"AynaLivePlayer/plugin/wylogin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
exitStatus, _ := panicwrap.BasicWrap(func(s string) {
|
||||
logger.Logger.Panic(s)
|
||||
os.Exit(1)
|
||||
return
|
||||
})
|
||||
if exitStatus >= 0 {
|
||||
os.Exit(exitStatus)
|
||||
}
|
||||
}
|
||||
var plugins = []controller.Plugin{diange.NewDiange(), qiege.NewQiege(), textinfo.NewTextInfo(), webinfo.NewWebInfo(),
|
||||
wylogin.NewWYLogin()}
|
||||
|
||||
func main() {
|
||||
fmt.Printf("BiliAudioBot Revive %s\n", config.VERSION)
|
||||
logger.Logger.SetLevel(logrus.DebugLevel)
|
||||
logger.Logger.Info("================Program Start================")
|
||||
logger.Logger.Infof("================Current Version: %s================", config.Version)
|
||||
controller.Initialize()
|
||||
controller.LoadPlugins(diange.NewDiange(), qiege.NewQiege(), textinfo.NewTextInfo())
|
||||
defer func() {
|
||||
controller.Destroy()
|
||||
config.SaveToConfigFile(config.CONFIG_PATH)
|
||||
i18n.SaveTranslation()
|
||||
}()
|
||||
controller.LoadPlugins(plugins...)
|
||||
gui.Initialize()
|
||||
gui.MainWindow.ShowAndRun()
|
||||
controller.ClosePlugins(plugins...)
|
||||
controller.Destroy()
|
||||
_ = config.SaveToConfigFile(config.ConfigPath)
|
||||
logger.Logger.Info("================Program End================")
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/plugin/textinfo"
|
||||
)
|
||||
|
||||
func main() {
|
||||
x := &textinfo.TextInfo{}
|
||||
x.Enable()
|
||||
x.RenderTemplates()
|
||||
}
|
||||
BIN
assets/icon.jpg
Normal file
BIN
assets/icon.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
@@ -96,6 +96,10 @@
|
||||
"en": "Please enter the ID or URL of the song you want to add.",
|
||||
"zh-CN": "输入歌单ID或者歌单网址。"
|
||||
},
|
||||
"gui.playlist.add.source": {
|
||||
"en": "Source",
|
||||
"zh-CN": "来源"
|
||||
},
|
||||
"gui.playlist.add.title": {
|
||||
"en": "Add Playlist",
|
||||
"zh-CN": "添加歌单"
|
||||
@@ -232,6 +236,10 @@
|
||||
"en": "Max Queue",
|
||||
"zh-CN": "最大点歌数"
|
||||
},
|
||||
"plugin.diange.source_cmd": {
|
||||
"en": "Source Command",
|
||||
"zh-CN": "来源点歌命令"
|
||||
},
|
||||
"plugin.diange.title": {
|
||||
"en": "Diange",
|
||||
"zh-CN": "点歌"
|
||||
@@ -240,6 +248,38 @@
|
||||
"en": "User",
|
||||
"zh-CN": "普通用户"
|
||||
},
|
||||
"plugin.neteaselogin.current_user": {
|
||||
"en": "Current User:",
|
||||
"zh-CN": "当前用户:"
|
||||
},
|
||||
"plugin.neteaselogin.current_user.notlogin": {
|
||||
"en": "Not Login",
|
||||
"zh-CN": "未登录"
|
||||
},
|
||||
"plugin.neteaselogin.description": {
|
||||
"en": "Netease User Login",
|
||||
"zh-CN": "网易云登录"
|
||||
},
|
||||
"plugin.neteaselogin.logout": {
|
||||
"en": "Logout",
|
||||
"zh-CN": "登出"
|
||||
},
|
||||
"plugin.neteaselogin.qr.finish": {
|
||||
"en": "Finish Scan",
|
||||
"zh-CN": "完成扫描后按我"
|
||||
},
|
||||
"plugin.neteaselogin.qr.new": {
|
||||
"en": "Get a new qr code",
|
||||
"zh-CN": "获取新二维码"
|
||||
},
|
||||
"plugin.neteaselogin.refresh": {
|
||||
"en": "Refresh",
|
||||
"zh-CN": "刷新状态"
|
||||
},
|
||||
"plugin.neteaselogin.title": {
|
||||
"en": "Netease Login",
|
||||
"zh-CN": "网易云登录"
|
||||
},
|
||||
"plugin.qiege.admin": {
|
||||
"en": "Admin",
|
||||
"zh-CN": "管理员"
|
||||
@@ -283,6 +323,50 @@
|
||||
"plugin.textinfo.title": {
|
||||
"en": "Text Output",
|
||||
"zh-CN": "文本输出"
|
||||
},
|
||||
"plugin.webinfo.description": {
|
||||
"en": "Web output configuration",
|
||||
"zh-CN": "web输出设置"
|
||||
},
|
||||
"plugin.webinfo.port": {
|
||||
"en": "Port",
|
||||
"zh-CN": "服务器端口"
|
||||
},
|
||||
"plugin.webinfo.server_control": {
|
||||
"en": "Control",
|
||||
"zh-CN": "操作"
|
||||
},
|
||||
"plugin.webinfo.server_control.restart": {
|
||||
"en": "Restart",
|
||||
"zh-CN": "重启"
|
||||
},
|
||||
"plugin.webinfo.server_control.start": {
|
||||
"en": "Start",
|
||||
"zh-CN": "启动"
|
||||
},
|
||||
"plugin.webinfo.server_control.stop": {
|
||||
"en": "Stop",
|
||||
"zh-CN": "停止"
|
||||
},
|
||||
"plugin.webinfo.server_status": {
|
||||
"en": "Server Status",
|
||||
"zh-CN": "服务器状态"
|
||||
},
|
||||
"plugin.webinfo.server_status.running": {
|
||||
"en": "Running",
|
||||
"zh-CN": "运行中"
|
||||
},
|
||||
"plugin.webinfo.server_status.stopped": {
|
||||
"en": "Stopped",
|
||||
"zh-CN": "已停止"
|
||||
},
|
||||
"plugin.webinfo.title": {
|
||||
"en": "Web Output",
|
||||
"zh-CN": "Web输出"
|
||||
},
|
||||
"plugin.webinfo.server_preview": {
|
||||
"en": "Server Preview",
|
||||
"zh-CN":"效果预览"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0
assets/webinfo/.gitkeep
Normal file
0
assets/webinfo/.gitkeep
Normal file
@@ -6,13 +6,18 @@ import (
|
||||
"path"
|
||||
)
|
||||
|
||||
const VERSION = "alpha 0.7.0"
|
||||
const (
|
||||
ProgramName = "卡西米尔唱片机"
|
||||
Version = "beta 0.9.0"
|
||||
)
|
||||
|
||||
const CONFIG_PATH = "./config.ini"
|
||||
const Assests_PATH = "./assets"
|
||||
const (
|
||||
ConfigPath = "./config.ini"
|
||||
AssetsPath = "./assets"
|
||||
)
|
||||
|
||||
func GetAssetPath(name string) string {
|
||||
return path.Join(Assests_PATH, name)
|
||||
return path.Join(AssetsPath, name)
|
||||
}
|
||||
|
||||
type Config interface {
|
||||
@@ -33,7 +38,7 @@ func LoadConfig(cfg Config) {
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
ConfigFile, err = ini.Load(CONFIG_PATH)
|
||||
ConfigFile, err = ini.Load(ConfigPath)
|
||||
if err != nil {
|
||||
fmt.Println("config not found, using default config")
|
||||
ConfigFile = ini.Empty()
|
||||
|
||||
@@ -3,8 +3,9 @@ package config
|
||||
import "github.com/sirupsen/logrus"
|
||||
|
||||
type _LogConfig struct {
|
||||
Path string
|
||||
Level logrus.Level
|
||||
Path string
|
||||
Level logrus.Level
|
||||
RedirectStderr bool
|
||||
}
|
||||
|
||||
func (c *_LogConfig) Name() string {
|
||||
@@ -12,6 +13,7 @@ func (c *_LogConfig) Name() string {
|
||||
}
|
||||
|
||||
var Log = &_LogConfig{
|
||||
Path: "./log.txt",
|
||||
Level: logrus.InfoLevel,
|
||||
Path: "./log.txt",
|
||||
Level: logrus.InfoLevel,
|
||||
RedirectStderr: false, // this should be true if it is in production mode.
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ func (c *_PlayerConfig) Name() string {
|
||||
}
|
||||
|
||||
var Player = &_PlayerConfig{
|
||||
Playlists: []string{"2382819181", "4987059624", "646548465"},
|
||||
PlaylistsProvider: []string{"netease", "netease", "netease"},
|
||||
Playlists: []string{"2382819181", "4987059624", "list1"},
|
||||
PlaylistsProvider: []string{"netease", "netease", "local"},
|
||||
PlaylistIndex: 0,
|
||||
PlaylistRandom: true,
|
||||
AudioDevice: "auto",
|
||||
|
||||
@@ -10,6 +10,6 @@ func (c *_ProviderConfig) Name() string {
|
||||
}
|
||||
|
||||
var Provider = &_ProviderConfig{
|
||||
Priority: []string{"local", "netease", "kuwo", "bilibili"},
|
||||
Priority: []string{"netease", "kuwo", "bilibili", "local", "bilibili-video"},
|
||||
LocalDir: "./music",
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
fmt.Println(SaveToConfigFile(CONFIG_PATH))
|
||||
fmt.Println(SaveToConfigFile(ConfigPath))
|
||||
}
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
|
||||
@@ -21,7 +21,7 @@ func PlayNext() {
|
||||
}
|
||||
|
||||
func Play(media *player.Media) {
|
||||
l().Info("prepare media")
|
||||
l().Infof("prepare media %s", media.Title)
|
||||
err := PrepareMedia(media)
|
||||
if err != nil {
|
||||
l().Warn("prepare media failed. try play next")
|
||||
@@ -32,6 +32,7 @@ func Play(media *player.Media) {
|
||||
AddToHistory(media)
|
||||
if err := MainPlayer.Play(media); err != nil {
|
||||
l().Warn("play failed", err)
|
||||
return
|
||||
}
|
||||
CurrentLyric.Reload(media.Lyric)
|
||||
// reset
|
||||
@@ -39,33 +40,40 @@ func Play(media *player.Media) {
|
||||
}
|
||||
|
||||
func Add(keyword string, user interface{}) {
|
||||
medias, err := Search(keyword)
|
||||
if err != nil {
|
||||
l().Warnf("search for %s, got error %s", keyword, err)
|
||||
return
|
||||
media := MediaMatch(keyword)
|
||||
if media == nil {
|
||||
medias, err := Search(keyword)
|
||||
if err != nil {
|
||||
l().Warnf("search for %s, got error %s", keyword, err)
|
||||
return
|
||||
}
|
||||
if len(medias) == 0 {
|
||||
l().Info("search for %s, got no result", keyword)
|
||||
return
|
||||
}
|
||||
media = medias[0]
|
||||
}
|
||||
if len(medias) == 0 {
|
||||
l().Info("search for %s, got no result", keyword)
|
||||
return
|
||||
}
|
||||
media := medias[0]
|
||||
media.User = user
|
||||
l().Infof("add media %s (%s)", media.Title, media.Artist)
|
||||
UserPlaylist.Insert(-1, media)
|
||||
}
|
||||
|
||||
func AddWithProvider(keyword string, pname string, user interface{}) {
|
||||
medias, err := provider.Search(pname, keyword)
|
||||
if err != nil {
|
||||
l().Warnf("search for %s, got error %s", keyword, err)
|
||||
return
|
||||
media := provider.MatchMedia(pname, keyword)
|
||||
if media == nil {
|
||||
medias, err := provider.Search(pname, keyword)
|
||||
if err != nil {
|
||||
l().Warnf("search for %s, got error %s", keyword, err)
|
||||
return
|
||||
}
|
||||
if len(medias) == 0 {
|
||||
l().Infof("search for %s, got no result", keyword)
|
||||
return
|
||||
}
|
||||
media = medias[0]
|
||||
}
|
||||
if len(medias) == 0 {
|
||||
l().Info("search for %s, got no result", keyword)
|
||||
}
|
||||
media := medias[0]
|
||||
media.User = user
|
||||
l().Info("add media %s (%s)", media.Title, media.Artist)
|
||||
l().Infof("add media %s (%s)", media.Title, media.Artist)
|
||||
UserPlaylist.Insert(-1, media)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,11 @@ import "AynaLivePlayer/player"
|
||||
func AddToHistory(media *player.Media) {
|
||||
l().Tracef("add media %s (%s) to history", media.Title, media.Artist)
|
||||
media = media.Copy()
|
||||
// reset url for future use
|
||||
media.Url = ""
|
||||
if History.Size() >= 1024 {
|
||||
History.Replace([]*player.Media{})
|
||||
}
|
||||
History.Push(media)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package controller
|
||||
type Plugin interface {
|
||||
Name() string
|
||||
Enable() error
|
||||
Disable() error
|
||||
}
|
||||
|
||||
func LoadPlugin(plugin Plugin) {
|
||||
@@ -17,3 +18,13 @@ func LoadPlugins(plugins ...Plugin) {
|
||||
LoadPlugin(plugin)
|
||||
}
|
||||
}
|
||||
|
||||
func ClosePlugins(plugins ...Plugin) {
|
||||
for _, plugin := range plugins {
|
||||
err := plugin.Disable()
|
||||
if err != nil {
|
||||
l().Warnf("Failed to close plugin: %s, %s", plugin.Name(), err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,27 +8,45 @@ import (
|
||||
|
||||
func PrepareMedia(media *player.Media) error {
|
||||
var err error
|
||||
if media.Title == "" || media.Cover == "" {
|
||||
if media.Title == "" || !media.Cover.Exists() {
|
||||
l().Trace("fetching media info")
|
||||
if err = provider.UpdateMedia(media); err != nil {
|
||||
l().Warn("fail to prepare media when fetch info", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if media.Url == "" {
|
||||
l().Trace("fetching media url")
|
||||
if err = provider.UpdateMediaUrl(media); err != nil {
|
||||
l().Warn("fail to prepare media when url", err)
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
if media.Lyric == "" {
|
||||
l().Trace("fetching media lyric")
|
||||
if err = provider.UpdateMediaLyric(media); err != nil {
|
||||
l().Warn("fail to prepare media when lyric", err)
|
||||
}
|
||||
|
||||
}
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func MediaMatch(keyword string) *player.Media {
|
||||
l().Infof("Match media for %s", keyword)
|
||||
for _, p := range config.Provider.Priority {
|
||||
if pr, ok := provider.Providers[p]; ok {
|
||||
m := pr.MatchMedia(keyword)
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
if err := provider.UpdateMedia(m); err == nil {
|
||||
return m
|
||||
}
|
||||
} else {
|
||||
l().Warnf("Provider %s not exist", p)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Search(keyword string) ([]*player.Media, error) {
|
||||
|
||||
17
go.mod
17
go.mod
@@ -4,28 +4,19 @@ go 1.16
|
||||
|
||||
require (
|
||||
fyne.io/fyne/v2 v2.1.4
|
||||
github.com/BurntSushi/toml v0.4.1
|
||||
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9
|
||||
github.com/XiaoMengXinX/Music163Api-Go v0.1.26
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.1
|
||||
github.com/aynakeya/blivedm v0.1.3
|
||||
github.com/aynakeya/go-mpv v0.0.4
|
||||
github.com/go-ole/go-ole v1.2.6
|
||||
github.com/dhowden/tag v0.0.0-20220618230019-adf36e896086
|
||||
github.com/go-resty/resty/v2 v2.7.0
|
||||
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/jinzhu/copier v0.3.5
|
||||
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca
|
||||
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b
|
||||
github.com/mitchellh/panicwrap v1.0.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/spf13/cast v1.3.1
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/tidwall/gjson v1.14.1
|
||||
github.com/urfave/cli/v2 v2.3.0
|
||||
golang.org/x/mod v0.4.2
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
||||
golang.org/x/tools v0.1.5
|
||||
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459
|
||||
gopkg.in/ini.v1 v1.66.4
|
||||
)
|
||||
|
||||
|
||||
27
go.sum
27
go.sum
@@ -1,21 +1,21 @@
|
||||
fyne.io/fyne/v2 v2.1.4 h1:bt1+28++kAzRzPB0GM2EuSV4cnl8rXNX4cjfd8G06Rc=
|
||||
fyne.io/fyne/v2 v2.1.4/go.mod h1:p+E/Dh+wPW8JwR2DVcsZ9iXgR9ZKde80+Y+40Is54AQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
|
||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9 h1:1ltqoej5GtaWF8jaiA49HwsZD459jqm9YFz9ZtMFpQA=
|
||||
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
|
||||
github.com/XiaoMengXinX/Music163Api-Go v0.1.26 h1:Nybor5okI8C0jzAiRvGfpLHdDrPqUbjx5kXWIZDX6pw=
|
||||
github.com/XiaoMengXinX/Music163Api-Go v0.1.26/go.mod h1:kLU/CkLxKnEJFCge0URvQ0lHt6ImoG1/2aVeNbgV2RQ=
|
||||
github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw=
|
||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ=
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dhowden/itl v0.0.0-20170329215456-9fbe21093131/go.mod h1:eVWQJVQ67aMvYhpkDwaH2Goy2vo6v8JCMfGXfQ9sPtw=
|
||||
github.com/dhowden/plist v0.0.0-20141002110153-5db6e0d9931a/go.mod h1:sLjdR6uwx3L6/Py8F+QgAfeiuY87xuYGwCDqRFrvCzw=
|
||||
github.com/dhowden/tag v0.0.0-20220618230019-adf36e896086 h1:ORubSQoKnncsBnR4zD9CuYFJCPOCuSNEpWEZrDdBXkc=
|
||||
github.com/dhowden/tag v0.0.0-20220618230019-adf36e896086/go.mod h1:Z3Lomva4pyMWYezjMAU5QWRh0p1VvO4199OHlFnyKkM=
|
||||
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA=
|
||||
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
@@ -24,7 +24,6 @@ github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f h1:s0O46d8fPwk9kU4k1jj76w
|
||||
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be h1:Z28GdQBfKOL8tNHjvaDn3wHDO7AzTRkmAXvHvnopp98=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q=
|
||||
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
|
||||
@@ -35,36 +34,32 @@ github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1E
|
||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526 h1:NfuKjkj/Xc2z1xZIj+EmNCm5p1nKJPyw3F4E20usXvg=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
|
||||
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
|
||||
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca h1:ozPUX9TKQZVek4lZWYRsQo7uS8vJ+q4OOHvRhHiCLfU=
|
||||
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b h1:tLSDWcFhT0WRlnsFszh4iaFTexWF8mmccGTk88Siq7Q=
|
||||
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
|
||||
github.com/mitchellh/panicwrap v1.0.0 h1:67zIyVakCIvcs69A0FGfZjBdPleaonSgGlXRSRlb6fE=
|
||||
github.com/mitchellh/panicwrap v1.0.0/go.mod h1:pKvZHwWrZowLUzftuFq7coarnxbBXU4aQh3N0BJOeeA=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
@@ -86,8 +81,9 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
|
||||
github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459 h1:x9pIfbdIjnw+Ylb2vE27Gtqb7BDmfR+nLcJwvbJh98U=
|
||||
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459/go.mod h1:nFvuG3SWu3VWqobG3cX8nt57wXU0OOFapeCs/8axIuM=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.3.8 h1:Nw158Q8QN+CPgTmVRByhVwapp8Mm1e2blinhmx4wx5E=
|
||||
github.com/yuin/goldmark v1.3.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
@@ -95,7 +91,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
|
||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -122,11 +117,9 @@ golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
|
||||
@@ -22,6 +22,10 @@ func (t *TestConfig) CreatePanel() fyne.CanvasObject {
|
||||
}
|
||||
|
||||
func createConfigLayout() fyne.CanvasObject {
|
||||
// initialize config panels
|
||||
for _, c := range ConfigList {
|
||||
c.CreatePanel()
|
||||
}
|
||||
content := container.NewMax()
|
||||
entryList := widget.NewList(
|
||||
func() int {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/i18n"
|
||||
"AynaLivePlayer/logger"
|
||||
"AynaLivePlayer/resource"
|
||||
"fmt"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/app"
|
||||
@@ -29,13 +30,14 @@ func l() *logrus.Entry {
|
||||
}
|
||||
|
||||
func Initialize() {
|
||||
l().Info("Initializing GUI")
|
||||
os.Setenv("FYNE_FONT", config.GetAssetPath("msyh.ttc"))
|
||||
App = app.New()
|
||||
MainWindow = App.NewWindow(fmt.Sprintf("AynaLivePlayer Ver.%s", config.VERSION))
|
||||
MainWindow = App.NewWindow(fmt.Sprintf("%s Ver.%s", config.ProgramName, config.Version))
|
||||
|
||||
tabs := container.NewAppTabs(
|
||||
container.NewTabItem(i18n.T("gui.tab.player"),
|
||||
newPaddedBoarder(nil, createPlayController(), nil, nil, createPlaylist()),
|
||||
newPaddedBoarder(nil, createPlayControllerV2(), nil, nil, createPlaylist()),
|
||||
),
|
||||
container.NewTabItem(i18n.T("gui.tab.search"),
|
||||
newPaddedBoarder(createSearchBar(), nil, nil, nil, createSearchList()),
|
||||
@@ -55,7 +57,7 @@ func Initialize() {
|
||||
)
|
||||
|
||||
tabs.SetTabLocation(container.TabLocationTop)
|
||||
|
||||
MainWindow.SetIcon(fyne.NewStaticResource("icon", resource.ProgramIcon))
|
||||
MainWindow.SetContent(tabs)
|
||||
//MainWindow.Resize(fyne.NewSize(1280, 720))
|
||||
MainWindow.Resize(fyne.NewSize(960, 480))
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/player"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/storage"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
@@ -74,3 +79,28 @@ func newFixedSplitContainer(horizontal bool, leading, trailing fyne.CanvasObject
|
||||
fs.Split.BaseWidget.ExtendBaseWidget(s)
|
||||
return fs
|
||||
}
|
||||
|
||||
func newImageFromPlayerPicture(picture player.Picture) (*canvas.Image, error) {
|
||||
if picture.Data != nil {
|
||||
img := canvas.NewImageFromReader(bytes.NewReader(picture.Data), "cover")
|
||||
// return an error when img is nil
|
||||
if img == nil {
|
||||
return nil, errors.New("fail to read image")
|
||||
}
|
||||
return img, nil
|
||||
} else {
|
||||
uri, err := storage.ParseURI(picture.Url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if uri == nil {
|
||||
return nil, errors.New("fail to fail url")
|
||||
}
|
||||
img := canvas.NewImageFromURI(uri)
|
||||
if img == nil {
|
||||
// bug fix, return a new error to indicate fail to read an image
|
||||
return nil, errors.New("fail to read image")
|
||||
}
|
||||
return img, nil
|
||||
}
|
||||
}
|
||||
|
||||
52
gui/helper_async_button.go
Normal file
52
gui/helper_async_button.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
// AsyncButton is a Button that handle OnTapped handler asynchronously.
|
||||
type AsyncButton struct {
|
||||
widget.Button
|
||||
anim *fyne.Animation
|
||||
}
|
||||
|
||||
func NewAsyncButton(label string, tapped func()) *AsyncButton {
|
||||
button := &AsyncButton{
|
||||
Button: widget.Button{
|
||||
Text: label,
|
||||
OnTapped: tapped,
|
||||
},
|
||||
}
|
||||
button.ExtendBaseWidget(button)
|
||||
return button
|
||||
}
|
||||
|
||||
func NewAsyncButtonWithIcon(label string, icon fyne.Resource, tapped func()) *AsyncButton {
|
||||
button := &AsyncButton{
|
||||
Button: widget.Button{
|
||||
Text: label,
|
||||
Icon: icon,
|
||||
OnTapped: tapped,
|
||||
},
|
||||
}
|
||||
button.ExtendBaseWidget(button)
|
||||
return button
|
||||
}
|
||||
|
||||
func (b *AsyncButton) Tapped(e *fyne.PointEvent) {
|
||||
if b.Disabled() {
|
||||
return
|
||||
}
|
||||
|
||||
// missing animation
|
||||
b.Refresh()
|
||||
|
||||
if b.OnTapped != nil {
|
||||
b.Disable()
|
||||
go func() {
|
||||
b.OnTapped()
|
||||
b.Enable()
|
||||
}()
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/storage"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"github.com/aynakeya/go-mpv"
|
||||
@@ -33,8 +32,7 @@ type PlayControllerContainer struct {
|
||||
}
|
||||
|
||||
func (p *PlayControllerContainer) SetDefaultCover() {
|
||||
p.Cover.Resource = nil
|
||||
p.Cover.File = config.GetAssetPath("empty.png")
|
||||
p.Cover.Resource = ResEmptyImage
|
||||
p.Cover.Refresh()
|
||||
}
|
||||
|
||||
@@ -81,11 +79,7 @@ func registerPlayControllerHandler() {
|
||||
controller.Seek(0, true)
|
||||
}
|
||||
PlayController.ButtonSwitch.OnTapped = func() {
|
||||
if controller.Toggle() {
|
||||
PlayController.ButtonSwitch.Icon = theme.MediaPlayIcon()
|
||||
} else {
|
||||
PlayController.ButtonSwitch.Icon = theme.MediaStopIcon()
|
||||
}
|
||||
controller.Toggle()
|
||||
}
|
||||
PlayController.ButtonNext.OnTapped = func() {
|
||||
controller.PlayNext()
|
||||
@@ -106,7 +100,7 @@ func registerPlayControllerHandler() {
|
||||
if property.Data.(mpv.Node).Value.(bool) {
|
||||
PlayController.ButtonSwitch.Icon = theme.MediaPlayIcon()
|
||||
} else {
|
||||
PlayController.ButtonSwitch.Icon = theme.MediaStopIcon()
|
||||
PlayController.ButtonSwitch.Icon = theme.MediaPauseIcon()
|
||||
}
|
||||
}) != nil {
|
||||
l().Error("fail to register handler for switch button with property pause")
|
||||
@@ -186,30 +180,73 @@ func registerPlayControllerHandler() {
|
||||
controller.MainPlayer.EventHandler.RegisterA(player.EventPlay, "gui.player.updateinfo", func(event *event.Event) {
|
||||
l().Debug("receive EventPlay update player info")
|
||||
media := event.Data.(player.PlayEvent).Media
|
||||
//PlayController.Title.SetText(
|
||||
// util.StringNormalize(media.Title, 16, 16))
|
||||
//PlayController.Artist.SetText(
|
||||
// util.StringNormalize(media.Artist, 16, 16))
|
||||
PlayController.Title.SetText(
|
||||
util.StringNormalize(media.Title, 16, 16))
|
||||
media.Title)
|
||||
PlayController.Artist.SetText(
|
||||
util.StringNormalize(media.Artist, 16, 16))
|
||||
media.Artist)
|
||||
PlayController.Username.SetText(media.ToUser().Name)
|
||||
if media.Cover == "" {
|
||||
if !media.Cover.Exists() {
|
||||
PlayController.SetDefaultCover()
|
||||
} else {
|
||||
uri, err := storage.ParseURI(media.Cover)
|
||||
if err != nil {
|
||||
l().Warn("fail to load parse cover url", media.Cover)
|
||||
}
|
||||
// async update
|
||||
go func() {
|
||||
img := canvas.NewImageFromURI(uri)
|
||||
if img == nil {
|
||||
picture, err := newImageFromPlayerPicture(media.Cover)
|
||||
if err != nil {
|
||||
l().Warn("fail to load parse cover url", media.Cover)
|
||||
PlayController.SetDefaultCover()
|
||||
return
|
||||
}
|
||||
PlayController.Cover.Resource = img.Resource
|
||||
PlayController.Cover.Resource = picture.Resource
|
||||
PlayController.Cover.Refresh()
|
||||
}()
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func createPlayControllerV2() fyne.CanvasObject {
|
||||
PlayController.Cover = canvas.NewImageFromResource(ResEmptyImage)
|
||||
PlayController.Cover.SetMinSize(fyne.NewSize(128, 128))
|
||||
PlayController.Cover.FillMode = canvas.ImageFillContain
|
||||
|
||||
PlayController.ButtonPrev = widget.NewButtonWithIcon("", theme.MediaSkipPreviousIcon(), func() {})
|
||||
PlayController.ButtonSwitch = widget.NewButtonWithIcon("", theme.MediaPlayIcon(), func() {})
|
||||
PlayController.ButtonNext = widget.NewButtonWithIcon("", theme.MediaSkipNextIcon(), func() {})
|
||||
|
||||
buttonsBox := container.NewHBox(PlayController.ButtonPrev, PlayController.ButtonSwitch, PlayController.ButtonNext)
|
||||
|
||||
PlayController.Volume = widget.NewSlider(0, 100)
|
||||
PlayController.ButtonLrc = widget.NewButton(i18n.T("gui.player.button.lrc"), func() {})
|
||||
|
||||
controls := container.NewPadded(container.NewBorder(nil, nil,
|
||||
buttonsBox, nil,
|
||||
container.NewGridWithColumns(
|
||||
2,
|
||||
container.NewMax(),
|
||||
container.NewBorder(nil, nil, widget.NewIcon(theme.VolumeMuteIcon()), PlayController.ButtonLrc,
|
||||
PlayController.Volume)),
|
||||
))
|
||||
|
||||
PlayController.Progress = widget.NewSlider(0, 1000)
|
||||
PlayController.CurrentTime = widget.NewLabel("0:00")
|
||||
PlayController.TotalTime = widget.NewLabel("0:00")
|
||||
progressItem := container.NewBorder(nil, nil,
|
||||
PlayController.CurrentTime,
|
||||
PlayController.TotalTime,
|
||||
PlayController.Progress)
|
||||
|
||||
PlayController.Title = widget.NewLabel("Title")
|
||||
PlayController.Artist = widget.NewLabel("Artist")
|
||||
PlayController.Username = widget.NewLabel("Username")
|
||||
|
||||
playInfo := container.NewBorder(nil, nil, nil, PlayController.Username,
|
||||
container.NewHBox(PlayController.Title, PlayController.Artist))
|
||||
|
||||
registerPlayControllerHandler()
|
||||
|
||||
return container.NewBorder(nil, nil, container.NewHBox(PlayController.Cover, widget.NewSeparator()), nil,
|
||||
container.NewVBox(playInfo, progressItem, controls))
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ func createPlaylists() fyne.CanvasObject {
|
||||
container.NewVBox(
|
||||
container.New(
|
||||
layout.NewFormLayout(),
|
||||
widget.NewLabel(i18n.T("gui.playlist.add.confirm")),
|
||||
widget.NewLabel(i18n.T("gui.playlist.add.source")),
|
||||
providerEntry,
|
||||
widget.NewLabel(i18n.T("gui.playlist.add.id_url")),
|
||||
idEntry,
|
||||
|
||||
8
gui/resource.go
Normal file
8
gui/resource.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/resource"
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
var ResEmptyImage = fyne.NewStaticResource("empty", resource.EmptyImage)
|
||||
@@ -2,6 +2,7 @@ package gui
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
@@ -11,5 +12,10 @@ type RoomLoggerContainer struct {
|
||||
var RoomLogger = &RoomLoggerContainer{}
|
||||
|
||||
func createRoomLogger() fyne.CanvasObject {
|
||||
return widget.NewLabel("广告位招租")
|
||||
//b := NewAsyncButton("ceshi", func() {
|
||||
// time.Sleep(time.Second * 5)
|
||||
//})
|
||||
return container.NewVBox(
|
||||
widget.NewLabel("广告位招租"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"AynaLivePlayer/config"
|
||||
nested "github.com/antonfisher/nested-logrus-formatter"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/virtuald/go-paniclog"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
@@ -13,15 +14,21 @@ var Logger *logrus.Logger
|
||||
func init() {
|
||||
Logger = logrus.New()
|
||||
Logger.SetLevel(config.Log.Level)
|
||||
Logger.SetFormatter(&nested.Formatter{
|
||||
FieldsOrder: []string{"Module"},
|
||||
HideKeys: true,
|
||||
NoColors: true,
|
||||
})
|
||||
file, err := os.OpenFile(config.Log.Path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
if err == nil {
|
||||
Logger.Out = io.MultiWriter(file, os.Stdout)
|
||||
} else {
|
||||
Logger.Info("Failed to log to file, using default stdout")
|
||||
}
|
||||
Logger.SetFormatter(&nested.Formatter{
|
||||
FieldsOrder: []string{"Module"},
|
||||
HideKeys: true,
|
||||
NoColors: true,
|
||||
})
|
||||
if config.Log.RedirectStderr {
|
||||
Logger.Info("panic/stderr redirect to log file")
|
||||
if _, err = paniclog.RedirectStderr(file); err != nil {
|
||||
Logger.Infof("Failed to redirect stderr to to file: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
music/list1/Lopu$,眠,Aevv - Love Letter <3.mp3
Normal file
BIN
music/list1/Lopu$,眠,Aevv - Love Letter <3.mp3
Normal file
Binary file not shown.
BIN
music/list1/著小生zoki,洛天依 - 【洛天依】影子小姐.mp3
Normal file
BIN
music/list1/著小生zoki,洛天依 - 【洛天依】影子小姐.mp3
Normal file
Binary file not shown.
@@ -41,6 +41,9 @@ func (l *Lyric) Reload(lyric string) {
|
||||
for index, time := range times {
|
||||
lrcs[index] = tmp[time]
|
||||
}
|
||||
if len(lrcs) == 0 {
|
||||
lrcs = append(lrcs, LyricLine{Time: 0, Lyric: ""})
|
||||
}
|
||||
lrcs = append(lrcs, LyricLine{
|
||||
Time: 99999999999,
|
||||
Lyric: "",
|
||||
|
||||
@@ -5,10 +5,19 @@ import (
|
||||
"github.com/jinzhu/copier"
|
||||
)
|
||||
|
||||
type Picture struct {
|
||||
Url string
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (p Picture) Exists() bool {
|
||||
return p.Url != "" || p.Data != nil
|
||||
}
|
||||
|
||||
type Media struct {
|
||||
Title string
|
||||
Artist string
|
||||
Cover string
|
||||
Cover Picture
|
||||
Album string
|
||||
Lyric string
|
||||
Url string
|
||||
|
||||
@@ -75,16 +75,17 @@ func (p *Player) l() *logrus.Entry {
|
||||
|
||||
func (p *Player) Play(media *Media) error {
|
||||
p.l().Infof("Play media %s", media.Url)
|
||||
p.l().Trace("set user-agent for mpv player")
|
||||
if val, ok := media.Header["user-agent"]; ok {
|
||||
if val, ok := media.Header["User-Agent"]; ok {
|
||||
p.l().Debug("set user-agent for mpv player")
|
||||
err := p.libmpv.SetPropertyString("user-agent", val)
|
||||
if err != nil {
|
||||
p.l().Warn("set player user-agent failed", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
p.l().Trace("set referrer for mpv player")
|
||||
if val, ok := media.Header["referrer"]; ok {
|
||||
|
||||
if val, ok := media.Header["Referer"]; ok {
|
||||
p.l().Debug("set referrer for mpv player")
|
||||
err := p.libmpv.SetPropertyString("referrer", val)
|
||||
if err != nil {
|
||||
p.l().Warn("set player referrer failed", err)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"AynaLivePlayer/i18n"
|
||||
"AynaLivePlayer/liveclient"
|
||||
"AynaLivePlayer/logger"
|
||||
"fmt"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/data/binding"
|
||||
@@ -29,6 +30,7 @@ type Diange struct {
|
||||
QueueMax int
|
||||
UserCoolDown int
|
||||
CustomCMD string
|
||||
SourceCMD []string
|
||||
cooldowns map[string]int
|
||||
panel fyne.CanvasObject
|
||||
}
|
||||
@@ -41,6 +43,7 @@ func NewDiange() *Diange {
|
||||
QueueMax: 128,
|
||||
UserCoolDown: -1,
|
||||
CustomCMD: "add",
|
||||
SourceCMD: make([]string, 0),
|
||||
cooldowns: make(map[string]int),
|
||||
}
|
||||
}
|
||||
@@ -51,21 +54,50 @@ func (d *Diange) Name() string {
|
||||
|
||||
func (d *Diange) Enable() error {
|
||||
config.LoadConfig(d)
|
||||
d.initCMD()
|
||||
controller.AddCommand(d)
|
||||
gui.AddConfigLayout(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Diange) Match(command string) bool {
|
||||
for _, c := range []string{"点歌", d.CustomCMD} {
|
||||
if command == c {
|
||||
return true
|
||||
func (d *Diange) Disable() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Diange) initCMD() {
|
||||
if len(d.SourceCMD) == len(config.Provider.Priority) {
|
||||
return
|
||||
}
|
||||
if len(d.SourceCMD) > len(config.Provider.Priority) {
|
||||
d.SourceCMD = d.SourceCMD[:len(config.Provider.Priority)]
|
||||
return
|
||||
}
|
||||
for i := len(d.SourceCMD); i < len(config.Provider.Priority); i++ {
|
||||
d.SourceCMD = append(d.SourceCMD, "点歌"+config.Provider.Priority[i])
|
||||
}
|
||||
}
|
||||
|
||||
// isCMD return int if the commmand name matches our command
|
||||
// -1 = not match, 0 = normal command, 1+ = source command
|
||||
func (d *Diange) isCMD(cmd string) int {
|
||||
if cmd == "点歌" || cmd == d.CustomCMD {
|
||||
return 0
|
||||
}
|
||||
fmt.Println(d.SourceCMD)
|
||||
for index, c := range d.SourceCMD {
|
||||
if cmd == c {
|
||||
return index + 1
|
||||
}
|
||||
}
|
||||
return false
|
||||
return -1
|
||||
}
|
||||
|
||||
func (d *Diange) Match(command string) bool {
|
||||
return d.isCMD(command) >= 0
|
||||
}
|
||||
|
||||
func (d *Diange) Execute(command string, args []string, danmu *liveclient.DanmuMessage) {
|
||||
l().Infof("%s(%s) Execute command: %s %s", danmu.User.Username, danmu.User.Uid, command, args)
|
||||
// if queue is full, return
|
||||
if controller.UserPlaylist.Size() >= d.QueueMax {
|
||||
l().Info("Queue is full, ignore diange")
|
||||
@@ -77,6 +109,7 @@ func (d *Diange) Execute(command string, args []string, danmu *liveclient.DanmuM
|
||||
l().Infof("User %s(%s) still in cool down period, diange failed", danmu.User.Username, danmu.User.Uid)
|
||||
return
|
||||
}
|
||||
cmdType := d.isCMD(command)
|
||||
keyword := strings.Join(args, " ")
|
||||
perm := d.UserPermission
|
||||
l().Trace("user permission check: ", perm)
|
||||
@@ -84,10 +117,15 @@ func (d *Diange) Execute(command string, args []string, danmu *liveclient.DanmuM
|
||||
l().Trace("privilege permission check: ", perm)
|
||||
perm = perm || (d.AdminPermission && (danmu.User.Admin))
|
||||
l().Trace("admin permission check: ", perm)
|
||||
if perm {
|
||||
// reset cool down
|
||||
d.cooldowns[danmu.User.Uid] = ct
|
||||
if !perm {
|
||||
return
|
||||
}
|
||||
// reset cool down
|
||||
d.cooldowns[danmu.User.Uid] = ct
|
||||
if cmdType == 0 {
|
||||
controller.Add(keyword, &danmu.User)
|
||||
} else {
|
||||
controller.AddWithProvider(keyword, config.Provider.Priority[cmdType-1], &danmu.User)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +159,17 @@ func (d *Diange) CreatePanel() fyne.CanvasObject {
|
||||
widget.NewLabel(i18n.T("plugin.diange.custom_cmd")), nil,
|
||||
widget.NewEntryWithData(binding.BindString(&d.CustomCMD)),
|
||||
)
|
||||
d.panel = container.NewVBox(dgPerm, dgQueue, dgCoolDown, dgShortCut)
|
||||
sourceCmds := []fyne.CanvasObject{}
|
||||
for i, _ := range d.SourceCMD {
|
||||
sourceCmds = append(
|
||||
sourceCmds,
|
||||
container.NewBorder(
|
||||
nil, nil, widget.NewLabel(config.Provider.Priority[i]), nil,
|
||||
widget.NewEntryWithData(binding.BindString(&d.SourceCMD[i]))))
|
||||
}
|
||||
dgSourceCMD := container.NewBorder(
|
||||
nil, nil, widget.NewLabel(i18n.T("plugin.diange.source_cmd")), nil,
|
||||
container.NewVBox(sourceCmds...))
|
||||
d.panel = container.NewVBox(dgPerm, dgQueue, dgCoolDown, dgShortCut, dgSourceCMD)
|
||||
return d.panel
|
||||
}
|
||||
|
||||
@@ -48,6 +48,10 @@ func (d *Qiege) Enable() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Qiege) Disable() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Qiege) Match(command string) bool {
|
||||
for _, c := range []string{"切歌", d.CustomCMD} {
|
||||
if command == c {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"fyne.io/fyne/v2/data/binding"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"github.com/aynakeya/go-mpv"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/sirupsen/logrus"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -41,6 +42,7 @@ type MediaInfo struct {
|
||||
Artist string
|
||||
Album string
|
||||
Username string
|
||||
Cover player.Picture
|
||||
}
|
||||
|
||||
type OutInfo struct {
|
||||
@@ -53,14 +55,16 @@ type OutInfo struct {
|
||||
}
|
||||
|
||||
type TextInfo struct {
|
||||
Rendering bool
|
||||
info OutInfo
|
||||
templates []*Template
|
||||
panel fyne.CanvasObject
|
||||
Rendering bool
|
||||
info OutInfo
|
||||
templates []*Template
|
||||
emptyCover []byte
|
||||
panel fyne.CanvasObject
|
||||
}
|
||||
|
||||
func NewTextInfo() *TextInfo {
|
||||
return &TextInfo{Rendering: true}
|
||||
b, _ := ioutil.ReadFile(config.GetAssetPath("empty.png"))
|
||||
return &TextInfo{Rendering: true, emptyCover: b}
|
||||
}
|
||||
|
||||
func (t *TextInfo) Title() string {
|
||||
@@ -102,6 +106,10 @@ func (t *TextInfo) Enable() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *TextInfo) Disable() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TextInfo) reloadTemplates() {
|
||||
var err error
|
||||
t.templates = make([]*Template, 0)
|
||||
@@ -159,6 +167,38 @@ func (t *TextInfo) RenderTemplates() {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TextInfo) OutputCover() {
|
||||
if !t.Rendering {
|
||||
return
|
||||
}
|
||||
if !t.info.Current.Cover.Exists() {
|
||||
err := ioutil.WriteFile(filepath.Join(Out_Path, "cover.jpg"), t.emptyCover, 0666)
|
||||
if err != nil {
|
||||
l().Warnf("write cover file failed: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if t.info.Current.Cover.Data != nil {
|
||||
err := ioutil.WriteFile(filepath.Join(Out_Path, "cover.jpg"), t.info.Current.Cover.Data, 0666)
|
||||
if err != nil {
|
||||
l().Warnf("write cover file failed: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
resp, err := resty.New().R().
|
||||
Get(t.info.Current.Cover.Url)
|
||||
if err != nil {
|
||||
l().Warnf("get cover %s content failed: %s", t.info.Current.Cover.Url, err)
|
||||
return
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(Out_Path, "cover.jpg"), resp.Body(), 0666)
|
||||
if err != nil {
|
||||
l().Warnf("write cover file failed: %s", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (t *TextInfo) registerHandlers() {
|
||||
controller.MainPlayer.EventHandler.RegisterA(player.EventPlay, "plugin.textinfo.current", func(event *event.Event) {
|
||||
t.info.Current = MediaInfo{
|
||||
@@ -166,9 +206,11 @@ func (t *TextInfo) registerHandlers() {
|
||||
Title: event.Data.(player.PlayEvent).Media.Title,
|
||||
Artist: event.Data.(player.PlayEvent).Media.Artist,
|
||||
Album: event.Data.(player.PlayEvent).Media.Album,
|
||||
Cover: event.Data.(player.PlayEvent).Media.Cover,
|
||||
Username: event.Data.(player.PlayEvent).Media.ToUser().Name,
|
||||
}
|
||||
t.RenderTemplates()
|
||||
t.OutputCover()
|
||||
})
|
||||
if controller.MainPlayer.ObserveProperty("time-pos", func(property *mpv.EventProperty) {
|
||||
if property.Data == nil {
|
||||
@@ -190,6 +232,7 @@ func (t *TextInfo) registerHandlers() {
|
||||
return
|
||||
}
|
||||
t.info.TotalTime = int(property.Data.(mpv.Node).Value.(float64))
|
||||
t.RenderTemplates()
|
||||
}) != nil {
|
||||
l().Error("fail to register handler for total time with property duration")
|
||||
}
|
||||
|
||||
33
plugin/webinfo/info.go
Normal file
33
plugin/webinfo/info.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package webinfo
|
||||
|
||||
import "AynaLivePlayer/player"
|
||||
|
||||
type MediaInfo struct {
|
||||
Index int
|
||||
Title string
|
||||
Artist string
|
||||
Album string
|
||||
Username string
|
||||
Cover player.Picture
|
||||
}
|
||||
|
||||
type OutInfo struct {
|
||||
Current MediaInfo
|
||||
CurrentTime int
|
||||
TotalTime int
|
||||
Lyric string
|
||||
Playlist []MediaInfo
|
||||
}
|
||||
|
||||
const (
|
||||
OutInfoC = "Current"
|
||||
OutInfoCT = "CurrentTime"
|
||||
OutInfoTT = "TotalTime"
|
||||
OutInfoL = "Lyric"
|
||||
OutInfoPL = "Playlist"
|
||||
)
|
||||
|
||||
type WebsocketData struct {
|
||||
Update string
|
||||
Data OutInfo
|
||||
}
|
||||
229
plugin/webinfo/server.go
Normal file
229
plugin/webinfo/server.go
Normal file
@@ -0,0 +1,229 @@
|
||||
package webinfo
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gorilla/websocket"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
type WebInfoServer struct {
|
||||
Info OutInfo
|
||||
Port int
|
||||
ServerMux *http.ServeMux
|
||||
Server *http.Server
|
||||
Clients map[*Client]int
|
||||
Running bool
|
||||
Store *TemplateStore
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
conn *websocket.Conn
|
||||
Data chan []byte
|
||||
Close chan byte
|
||||
}
|
||||
|
||||
func NewWebInfoServer(port int) *WebInfoServer {
|
||||
server := &WebInfoServer{
|
||||
Store: newTemplateStore(WebTemplateStorePath),
|
||||
Port: port,
|
||||
Info: OutInfo{Playlist: make([]MediaInfo, 0)},
|
||||
Clients: map[*Client]int{},
|
||||
}
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/", http.FileServer(http.Dir(config.GetAssetPath("webinfo"))))
|
||||
mux.HandleFunc("/ws/info", server.handleInfo)
|
||||
mux.HandleFunc("/api/info", server.getInfo)
|
||||
mux.HandleFunc("/api/template/list", server.tmplList)
|
||||
mux.HandleFunc("/api/template/get", server.tmplGet)
|
||||
mux.HandleFunc("/api/template/save", server.tmplSave)
|
||||
server.ServerMux = mux
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
func (s *WebInfoServer) tmplList(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
//w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
||||
d, _ := json.Marshal(s.Store.List())
|
||||
_, err := w.Write(d)
|
||||
if err != nil {
|
||||
lg.Warnf("/api/template/list error: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebInfoServer) tmplGet(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
name := r.URL.Query().Get("name")
|
||||
if name == "" {
|
||||
name = "default"
|
||||
}
|
||||
d, _ := json.Marshal(s.Store.Get(name))
|
||||
_, err := w.Write(d)
|
||||
if err != nil {
|
||||
lg.Warnf("/api/template/get error: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebInfoServer) tmplSave(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Method", "*")
|
||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
w.Header().Set("Access-Control-Expose-Headers", "*")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "*")
|
||||
lg.Info(r.Method)
|
||||
if r.Method == "OPTIONS" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
if err := r.ParseMultipartForm(1 << 16); err != nil {
|
||||
lg.Warnf("ParseForm() err: %v", err)
|
||||
return
|
||||
}
|
||||
name := r.FormValue("name")
|
||||
tmpl := r.FormValue("template")
|
||||
if name == "" {
|
||||
name = "default"
|
||||
}
|
||||
lg.Infof("change template %s", name)
|
||||
s.Store.Modify(name, tmpl)
|
||||
d, _ := json.Marshal(s.Store.Get(name))
|
||||
_, err := w.Write(d)
|
||||
if err != nil {
|
||||
lg.Warnf("/api/template/save error: %s", err)
|
||||
return
|
||||
}
|
||||
s.Store.Save(WebTemplateStorePath)
|
||||
}
|
||||
|
||||
func (s *WebInfoServer) getInfo(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
//w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
||||
d, _ := json.Marshal(s.Info)
|
||||
_, err := w.Write(d)
|
||||
if err != nil {
|
||||
lg.Warnf("api get info error: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebInfoServer) handleInfo(w http.ResponseWriter, r *http.Request) {
|
||||
lg.Debug("connection start")
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
lg.Warnf("upgrade error: %s", err)
|
||||
return
|
||||
}
|
||||
client := &Client{
|
||||
conn: conn,
|
||||
Data: make(chan []byte, 16),
|
||||
Close: make(chan byte, 1),
|
||||
}
|
||||
s.addClient(client)
|
||||
defer s.removeClient(client)
|
||||
go func() {
|
||||
for {
|
||||
_, _, err := client.conn.ReadMessage()
|
||||
if err != nil {
|
||||
client.Close <- 1
|
||||
}
|
||||
}
|
||||
}()
|
||||
for {
|
||||
lg.Trace("waiting for message")
|
||||
select {
|
||||
case data := <-client.Data:
|
||||
writer, err := client.conn.NextWriter(websocket.TextMessage)
|
||||
if err != nil {
|
||||
lg.Warn("get writer error", err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = writer.Write(data); err != nil {
|
||||
lg.Warn("send error:", err)
|
||||
return
|
||||
}
|
||||
if err = writer.Close(); err != nil {
|
||||
lg.Warnf("can't close writer: %s", err)
|
||||
return
|
||||
}
|
||||
case _ = <-client.Close:
|
||||
lg.Debug("client close")
|
||||
if err := client.conn.Close(); err != nil {
|
||||
lg.Warnf("close connection encouter an error: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebInfoServer) SendInfo(update string, info OutInfo) {
|
||||
for client := range s.Clients {
|
||||
d, _ := json.Marshal(WebsocketData{Update: update, Data: info})
|
||||
client.Data <- d
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WebInfoServer) addClient(c *Client) {
|
||||
s.lock.Lock()
|
||||
s.Clients[c] = 1
|
||||
s.lock.Unlock()
|
||||
}
|
||||
|
||||
func (s *WebInfoServer) removeClient(c *Client) {
|
||||
s.lock.Lock()
|
||||
close(c.Data)
|
||||
delete(s.Clients, c)
|
||||
s.lock.Unlock()
|
||||
}
|
||||
|
||||
func (s *WebInfoServer) Start() {
|
||||
lg.Debug("WebInfoServer starting...")
|
||||
s.Running = true
|
||||
go func() {
|
||||
s.Server = &http.Server{
|
||||
Addr: fmt.Sprintf("localhost:%d", s.Port),
|
||||
Handler: s.ServerMux,
|
||||
}
|
||||
err := s.Server.ListenAndServe()
|
||||
s.Running = false
|
||||
if err == http.ErrServerClosed {
|
||||
lg.Info("WebInfoServer closed")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
lg.Warnf("Failed to start webinfo server: %s", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *WebInfoServer) Stop() error {
|
||||
lg.Debug("WebInfoServer stopping...")
|
||||
s.lock.Lock()
|
||||
s.Clients = map[*Client]int{}
|
||||
s.lock.Unlock()
|
||||
return s.Server.Shutdown(context.TODO())
|
||||
}
|
||||
76
plugin/webinfo/template.go
Normal file
76
plugin/webinfo/template.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package webinfo
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/util"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
const WebTemplateStorePath = "./webtemplates.json"
|
||||
|
||||
type WebTemplate struct {
|
||||
Name string
|
||||
Template string
|
||||
}
|
||||
|
||||
type TemplateStore struct {
|
||||
Templates map[string]*WebTemplate
|
||||
}
|
||||
|
||||
func newTemplateStore(filename string) *TemplateStore {
|
||||
s := &TemplateStore{Templates: map[string]*WebTemplate{}}
|
||||
var templates []*WebTemplate
|
||||
file, err := ioutil.ReadFile(filename)
|
||||
if err == nil {
|
||||
_ = json.Unmarshal(file, &templates)
|
||||
}
|
||||
|
||||
for _, tmpl := range templates {
|
||||
s.Templates[tmpl.Name] = tmpl
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *TemplateStore) Save(filename string) {
|
||||
templates := make([]WebTemplate, 0)
|
||||
for _, tmp := range s.Templates {
|
||||
templates = append(templates, *tmp)
|
||||
}
|
||||
unescape, err := util.MarshalIndentUnescape(templates, "", " ")
|
||||
if err != nil {
|
||||
lg.Warnf("save web templates to %s failed: %s", filename, err)
|
||||
return
|
||||
}
|
||||
if err := ioutil.WriteFile(filename, []byte(unescape), 0666); err != nil {
|
||||
lg.Warnf("save web templates to %s failed: %s", filename, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TemplateStore) Get(name string) *WebTemplate {
|
||||
if t, ok := s.Templates[name]; ok {
|
||||
return t
|
||||
}
|
||||
t := &WebTemplate{Name: name, Template: "<p>Empty</p>"}
|
||||
s.Templates[name] = t
|
||||
return t
|
||||
}
|
||||
|
||||
func (s *TemplateStore) Modify(name string, content string) {
|
||||
if _, ok := s.Templates[name]; ok {
|
||||
s.Templates[name].Template = content
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TemplateStore) List() []string {
|
||||
names := make([]string, 0)
|
||||
for name, _ := range s.Templates {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func (s *TemplateStore) Delete(name string) {
|
||||
delete(s.Templates, name)
|
||||
}
|
||||
22
plugin/webinfo/template_test.go
Normal file
22
plugin/webinfo/template_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package webinfo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTemplateStore_Create(t *testing.T) {
|
||||
s := newTemplateStore(WebTemplateStorePath)
|
||||
s.Get("A")
|
||||
s.Get("B")
|
||||
s.Modify("A", "33333")
|
||||
s.Save(WebTemplateStorePath)
|
||||
}
|
||||
|
||||
func TestTemplateStore_Load(t *testing.T) {
|
||||
s := newTemplateStore(WebTemplateStorePath)
|
||||
fmt.Println(s.List())
|
||||
for name, tmpl := range s.Templates {
|
||||
fmt.Println(name, tmpl.Template)
|
||||
}
|
||||
}
|
||||
229
plugin/webinfo/webinfo.go
Normal file
229
plugin/webinfo/webinfo.go
Normal file
@@ -0,0 +1,229 @@
|
||||
package webinfo
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/controller"
|
||||
"AynaLivePlayer/event"
|
||||
"AynaLivePlayer/gui"
|
||||
"AynaLivePlayer/i18n"
|
||||
"AynaLivePlayer/logger"
|
||||
"AynaLivePlayer/player"
|
||||
"AynaLivePlayer/util"
|
||||
"fmt"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/data/binding"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"github.com/aynakeya/go-mpv"
|
||||
)
|
||||
|
||||
const MODULE_PLGUIN_WEBINFO = "plugin.webinfo"
|
||||
|
||||
var lg = logger.Logger.WithField("Module", MODULE_PLGUIN_WEBINFO)
|
||||
|
||||
type WebInfo struct {
|
||||
Port int
|
||||
server *WebInfoServer
|
||||
panel fyne.CanvasObject
|
||||
}
|
||||
|
||||
func NewWebInfo() *WebInfo {
|
||||
return &WebInfo{
|
||||
Port: 4000,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WebInfo) Name() string {
|
||||
return "WebInfo"
|
||||
}
|
||||
|
||||
func (w *WebInfo) Title() string {
|
||||
return i18n.T("plugin.webinfo.title")
|
||||
}
|
||||
|
||||
func (w *WebInfo) Description() string {
|
||||
return i18n.T("plugin.webinfo.description")
|
||||
}
|
||||
|
||||
func (w *WebInfo) Enable() error {
|
||||
config.LoadConfig(w)
|
||||
w.server = NewWebInfoServer(w.Port)
|
||||
lg.Info("starting web backend server")
|
||||
w.server.Start()
|
||||
w.registerHandlers()
|
||||
gui.AddConfigLayout(w)
|
||||
lg.Info("webinfo loaded")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WebInfo) Disable() error {
|
||||
lg.Info("closing webinfo backend server")
|
||||
if err := w.server.Stop(); err != nil {
|
||||
lg.Warnf("stop webinfo server encouter an error: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *WebInfo) registerHandlers() {
|
||||
controller.MainPlayer.EventHandler.RegisterA(player.EventPlay, "plugin.webinfo.current", func(event *event.Event) {
|
||||
t.server.Info.Current = MediaInfo{
|
||||
Index: 0,
|
||||
Title: event.Data.(player.PlayEvent).Media.Title,
|
||||
Artist: event.Data.(player.PlayEvent).Media.Artist,
|
||||
Album: event.Data.(player.PlayEvent).Media.Album,
|
||||
Cover: event.Data.(player.PlayEvent).Media.Cover,
|
||||
Username: event.Data.(player.PlayEvent).Media.ToUser().Name,
|
||||
}
|
||||
t.server.SendInfo(
|
||||
OutInfoC,
|
||||
OutInfo{Current: t.server.Info.Current},
|
||||
)
|
||||
})
|
||||
if controller.MainPlayer.ObserveProperty("time-pos", func(property *mpv.EventProperty) {
|
||||
if property.Data == nil {
|
||||
t.server.Info.CurrentTime = 0
|
||||
return
|
||||
}
|
||||
ct := int(property.Data.(mpv.Node).Value.(float64))
|
||||
if ct == t.server.Info.CurrentTime {
|
||||
return
|
||||
}
|
||||
t.server.Info.CurrentTime = ct
|
||||
t.server.SendInfo(
|
||||
OutInfoCT,
|
||||
OutInfo{CurrentTime: t.server.Info.CurrentTime},
|
||||
)
|
||||
}) != nil {
|
||||
lg.Error("register time-pos handler failed")
|
||||
}
|
||||
if controller.MainPlayer.ObserveProperty("duration", func(property *mpv.EventProperty) {
|
||||
if property.Data == nil {
|
||||
t.server.Info.TotalTime = 0
|
||||
return
|
||||
}
|
||||
t.server.Info.TotalTime = int(property.Data.(mpv.Node).Value.(float64))
|
||||
t.server.SendInfo(
|
||||
OutInfoTT,
|
||||
OutInfo{TotalTime: t.server.Info.TotalTime},
|
||||
)
|
||||
}) != nil {
|
||||
lg.Error("fail to register handler for total time with property duration")
|
||||
}
|
||||
controller.UserPlaylist.Handler.RegisterA(player.EventPlaylistUpdate, "plugin.webinfo.playlist", func(event *event.Event) {
|
||||
pl := make([]MediaInfo, 0)
|
||||
e := event.Data.(player.PlaylistUpdateEvent)
|
||||
e.Playlist.Lock.RLock()
|
||||
for index, m := range e.Playlist.Playlist {
|
||||
pl = append(pl, MediaInfo{
|
||||
Index: index,
|
||||
Title: m.Title,
|
||||
Artist: m.Artist,
|
||||
Album: m.Album,
|
||||
Username: m.ToUser().Name,
|
||||
})
|
||||
}
|
||||
e.Playlist.Lock.RUnlock()
|
||||
t.server.Info.Playlist = pl
|
||||
t.server.SendInfo(
|
||||
OutInfoPL,
|
||||
OutInfo{Playlist: t.server.Info.Playlist},
|
||||
)
|
||||
})
|
||||
controller.CurrentLyric.Handler.RegisterA(player.EventLyricUpdate, "plugin.webinfo.lyric", func(event *event.Event) {
|
||||
lrcLine := event.Data.(player.LyricUpdateEvent).Lyric
|
||||
t.server.Info.Lyric = lrcLine.Lyric
|
||||
t.server.SendInfo(
|
||||
OutInfoL,
|
||||
OutInfo{Lyric: t.server.Info.Lyric},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func (w *WebInfo) getServerStatusText() string {
|
||||
if w.server.Running {
|
||||
return i18n.T("plugin.webinfo.server_status.running")
|
||||
} else {
|
||||
return i18n.T("plugin.webinfo.server_status.stopped")
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WebInfo) getServerUrl() string {
|
||||
return fmt.Sprintf("http://localhost:%d/#/previewV2", w.Port)
|
||||
}
|
||||
|
||||
func (w *WebInfo) CreatePanel() fyne.CanvasObject {
|
||||
if w.panel != nil {
|
||||
return w.panel
|
||||
}
|
||||
statusText := widget.NewLabel("")
|
||||
serverStatus := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("plugin.webinfo.server_status")),
|
||||
statusText,
|
||||
)
|
||||
statusText.SetText(w.getServerStatusText())
|
||||
serverPort := container.NewBorder(nil, nil,
|
||||
widget.NewLabel(i18n.T("plugin.webinfo.port")), nil,
|
||||
widget.NewEntryWithData(binding.IntToString(binding.BindInt(&w.Port))),
|
||||
)
|
||||
serverUrl := widget.NewHyperlink(w.getServerUrl(), util.UrlMustParse(w.getServerUrl()))
|
||||
serverPreview := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("plugin.webinfo.server_preview")),
|
||||
serverUrl,
|
||||
)
|
||||
stopBtn := gui.NewAsyncButtonWithIcon(
|
||||
i18n.T("plugin.webinfo.server_control.stop"),
|
||||
theme.MediaStopIcon(),
|
||||
func() {
|
||||
if !w.server.Running {
|
||||
return
|
||||
}
|
||||
lg.Info("User try stop webinfo server")
|
||||
err := w.server.Stop()
|
||||
if err != nil {
|
||||
lg.Warnf("stop server have error: %s", err)
|
||||
return
|
||||
}
|
||||
statusText.SetText(w.getServerStatusText())
|
||||
},
|
||||
)
|
||||
startBtn := gui.NewAsyncButtonWithIcon(
|
||||
i18n.T("plugin.webinfo.server_control.start"),
|
||||
theme.MediaPlayIcon(),
|
||||
func() {
|
||||
if w.server.Running {
|
||||
return
|
||||
}
|
||||
lg.Infof("User try start webinfo server with port %d", w.Port)
|
||||
w.server.Port = w.Port
|
||||
w.server.Start()
|
||||
statusText.SetText(w.getServerStatusText())
|
||||
serverUrl.SetText(w.getServerUrl())
|
||||
_ = serverUrl.SetURLFromString(w.getServerUrl())
|
||||
},
|
||||
)
|
||||
restartBtn := gui.NewAsyncButtonWithIcon(
|
||||
i18n.T("plugin.webinfo.server_control.restart"),
|
||||
theme.MediaReplayIcon(),
|
||||
func() {
|
||||
lg.Infof("User try restart webinfo server with port %d", w.Port)
|
||||
if w.server.Running {
|
||||
if err := w.server.Stop(); err != nil {
|
||||
lg.Warnf("stop server have error: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
w.server.Port = w.Port
|
||||
w.server.Start()
|
||||
statusText.SetText(w.getServerStatusText())
|
||||
serverUrl.SetText(w.getServerUrl())
|
||||
_ = serverUrl.SetURLFromString(w.getServerUrl())
|
||||
},
|
||||
)
|
||||
ctrlBtns := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("plugin.webinfo.server_control")),
|
||||
startBtn, stopBtn, restartBtn,
|
||||
)
|
||||
w.panel = container.NewVBox(serverStatus, serverPreview, serverPort, ctrlBtns)
|
||||
return w.panel
|
||||
}
|
||||
164
plugin/wylogin/wylogin.go
Normal file
164
plugin/wylogin/wylogin.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package wylogin
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/gui"
|
||||
"AynaLivePlayer/i18n"
|
||||
"AynaLivePlayer/logger"
|
||||
"AynaLivePlayer/provider"
|
||||
"bytes"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
qrcode "github.com/skip2/go-qrcode"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const MODULE_PLGUIN_NETEASELOGIN = "plugin.neteaselogin"
|
||||
|
||||
var lg = logger.Logger.WithField("Module", MODULE_PLGUIN_NETEASELOGIN)
|
||||
|
||||
type WYLogin struct {
|
||||
MusicU string
|
||||
CSRF string
|
||||
panel fyne.CanvasObject
|
||||
}
|
||||
|
||||
func NewWYLogin() *WYLogin {
|
||||
return &WYLogin{
|
||||
MusicU: "MUSIC_U=;",
|
||||
CSRF: "__csrf=;",
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WYLogin) Name() string {
|
||||
return "NeteaseLogin"
|
||||
}
|
||||
|
||||
func (w *WYLogin) Enable() error {
|
||||
config.LoadConfig(w)
|
||||
w.loadCookie()
|
||||
gui.AddConfigLayout(w)
|
||||
go func() {
|
||||
lg.Info("updating netease status")
|
||||
provider.NeteaseAPI.UpdateStatus()
|
||||
lg.Info("finish updating netease status")
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WYLogin) Disable() error {
|
||||
w.saveCookie()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WYLogin) loadCookie() {
|
||||
provider.NeteaseAPI.ReqData.Cookies = (&http.Response{
|
||||
Header: map[string][]string{
|
||||
"Set-Cookie": []string{w.MusicU, w.CSRF},
|
||||
},
|
||||
}).Cookies()
|
||||
}
|
||||
|
||||
func (w *WYLogin) saveCookie() {
|
||||
for _, c := range provider.NeteaseAPI.ReqData.Cookies {
|
||||
if c.Name == "MUSIC_U" {
|
||||
w.MusicU = c.String()
|
||||
}
|
||||
if c.Name == "__csrf" {
|
||||
w.CSRF = c.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WYLogin) Title() string {
|
||||
return i18n.T("plugin.neteaselogin.title")
|
||||
}
|
||||
|
||||
func (w *WYLogin) Description() string {
|
||||
return i18n.T("plugin.neteaselogin.description")
|
||||
}
|
||||
|
||||
func (w *WYLogin) CreatePanel() fyne.CanvasObject {
|
||||
if w.panel != nil {
|
||||
return w.panel
|
||||
}
|
||||
currentUser := widget.NewLabel(i18n.T("plugin.neteaselogin.current_user.notlogin"))
|
||||
currentStatus := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("plugin.neteaselogin.current_user")),
|
||||
currentUser)
|
||||
|
||||
refreshBtn := gui.NewAsyncButton(
|
||||
i18n.T("plugin.neteaselogin.refresh"),
|
||||
func() {
|
||||
provider.NeteaseAPI.UpdateStatus()
|
||||
if provider.NeteaseAPI.IsLogin() {
|
||||
currentUser.SetText(provider.NeteaseAPI.Nickname())
|
||||
} else {
|
||||
currentUser.SetText(i18n.T("plugin.neteaselogin.current_user.notlogin"))
|
||||
}
|
||||
|
||||
},
|
||||
)
|
||||
logoutBtn := gui.NewAsyncButton(
|
||||
i18n.T("plugin.neteaselogin.logout"),
|
||||
func() {
|
||||
provider.NeteaseAPI.Logout()
|
||||
currentUser.SetText(i18n.T("plugin.neteaselogin.current_user.notlogin"))
|
||||
},
|
||||
)
|
||||
controlBtns := container.NewHBox(refreshBtn, logoutBtn)
|
||||
qrcodeImg := canvas.NewImageFromResource(gui.ResEmptyImage)
|
||||
qrcodeImg.SetMinSize(fyne.NewSize(200, 200))
|
||||
qrcodeImg.FillMode = canvas.ImageFillContain
|
||||
var key string
|
||||
qrStatus := widget.NewLabel("AAAAAAAA")
|
||||
qrStatus.SetText("")
|
||||
newQrBtn := gui.NewAsyncButton(
|
||||
i18n.T("plugin.neteaselogin.qr.new"),
|
||||
func() {
|
||||
qrStatus.SetText("")
|
||||
lg.Info("getting a new qr code for login")
|
||||
key = provider.NeteaseAPI.GetQrLoginKey()
|
||||
if key == "" {
|
||||
lg.Warn("fail to get qr code key")
|
||||
return
|
||||
}
|
||||
lg.Debugf("trying encode url %s to qrcode", provider.NeteaseAPI.GetQrLoginUrl(key))
|
||||
data, err := qrcode.Encode(provider.NeteaseAPI.GetQrLoginUrl(key), qrcode.Medium, 256)
|
||||
if err != nil {
|
||||
lg.Warnf("generate qr code failed: %s", err)
|
||||
return
|
||||
}
|
||||
lg.Debug("create img from raw data")
|
||||
pic := canvas.NewImageFromReader(bytes.NewReader(data), "qrcode")
|
||||
qrcodeImg.Resource = pic.Resource
|
||||
qrcodeImg.Refresh()
|
||||
},
|
||||
)
|
||||
finishQrBtn := gui.NewAsyncButton(
|
||||
i18n.T("plugin.neteaselogin.qr.finish"),
|
||||
func() {
|
||||
if key == "" {
|
||||
return
|
||||
}
|
||||
lg.Info("checking qr status")
|
||||
ok, msg := provider.NeteaseAPI.CheckQrLogin(key)
|
||||
qrStatus.SetText(msg)
|
||||
if ok {
|
||||
key = ""
|
||||
qrcodeImg.Resource = gui.ResEmptyImage
|
||||
qrcodeImg.Refresh()
|
||||
}
|
||||
},
|
||||
)
|
||||
loginPanel := container.NewCenter(
|
||||
container.NewVBox(
|
||||
qrcodeImg,
|
||||
container.NewHBox(newQrBtn, finishQrBtn, qrStatus),
|
||||
),
|
||||
)
|
||||
w.panel = container.NewVBox(controlBtns, currentStatus, loginPanel)
|
||||
return w.panel
|
||||
}
|
||||
@@ -5,12 +5,15 @@ import (
|
||||
"fmt"
|
||||
"github.com/tidwall/gjson"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type Bilibili struct {
|
||||
InfoApi string
|
||||
FileApi string
|
||||
SearchApi string
|
||||
IdRegex0 *regexp.Regexp
|
||||
IdRegex1 *regexp.Regexp
|
||||
}
|
||||
|
||||
func _newBilibili() *Bilibili {
|
||||
@@ -18,6 +21,8 @@ func _newBilibili() *Bilibili {
|
||||
InfoApi: "https://www.bilibili.com/audio/music-service-c/web/song/info?sid=%s",
|
||||
FileApi: "https://api.bilibili.com/audio/music-service-c/url?device=phone&mid=8047632&mobi_app=iphone&platform=ios&privilege=2&songid=%s&quality=2",
|
||||
SearchApi: "https://api.bilibili.com/audio/music-service-c/s?search_type=music&keyword=%s&page=1&pagesize=100",
|
||||
IdRegex0: regexp.MustCompile("^[0-9]+"),
|
||||
IdRegex1: regexp.MustCompile("^au[0-9]+"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +37,26 @@ func (b *Bilibili) GetName() string {
|
||||
return "bilibili"
|
||||
}
|
||||
|
||||
func (b *Bilibili) MatchMedia(keyword string) *player.Media {
|
||||
if id := b.IdRegex0.FindString(keyword); id != "" {
|
||||
return &player.Media{
|
||||
Meta: Meta{
|
||||
Name: b.GetName(),
|
||||
Id: id,
|
||||
},
|
||||
}
|
||||
}
|
||||
if id := b.IdRegex1.FindString(keyword); id != "" {
|
||||
return &player.Media{
|
||||
Meta: Meta{
|
||||
Name: b.GetName(),
|
||||
Id: id[2:],
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bilibili) FormatPlaylistUrl(uri string) string {
|
||||
return ""
|
||||
}
|
||||
@@ -51,7 +76,7 @@ func (b *Bilibili) Search(keyword string) ([]*player.Media, error) {
|
||||
gjson.Get(resp, "data.result").ForEach(func(key, value gjson.Result) bool {
|
||||
result = append(result, &player.Media{
|
||||
Title: value.Get("title").String(),
|
||||
Cover: value.Get("cover").String(),
|
||||
Cover: player.Picture{Url: value.Get("cover").String()},
|
||||
Artist: value.Get("author").String(),
|
||||
Meta: Meta{
|
||||
Name: b.GetName(),
|
||||
@@ -74,7 +99,7 @@ func (b *Bilibili) UpdateMedia(media *player.Media) error {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
media.Title = gjson.Get(resp, "data.title").String()
|
||||
media.Cover = gjson.Get(resp, "data.cover").String()
|
||||
media.Cover.Url = gjson.Get(resp, "data.cover").String()
|
||||
media.Artist = gjson.Get(resp, "data.author").String()
|
||||
media.Album = media.Title
|
||||
return nil
|
||||
@@ -91,7 +116,6 @@ func (b *Bilibili) UpdateMediaUrl(media *player.Media) error {
|
||||
media.Header = map[string]string{
|
||||
"user-agent": "BiliMusic/2.233.3",
|
||||
}
|
||||
fmt.Println(fmt.Sprintf(b.InfoApi, media.Meta.(Meta).Id))
|
||||
uri := gjson.Get(resp, "data.cdns.0").String()
|
||||
if uri == "" {
|
||||
return ErrorExternalApi
|
||||
|
||||
156
provider/bilivideo.go
Normal file
156
provider/bilivideo.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/player"
|
||||
"AynaLivePlayer/util"
|
||||
"fmt"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/tidwall/gjson"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type BilibiliVideo struct {
|
||||
InfoApi string
|
||||
FileApi string
|
||||
SearchApi string
|
||||
BVRegex *regexp.Regexp
|
||||
IdRegex *regexp.Regexp
|
||||
PageRegex *regexp.Regexp
|
||||
header map[string]string
|
||||
}
|
||||
|
||||
func _newBilibiliVideo() *BilibiliVideo {
|
||||
return &BilibiliVideo{
|
||||
InfoApi: "https://api.bilibili.com/x/web-interface/view/detail?bvid=%s&aid=&jsonp=jsonp",
|
||||
FileApi: "https://api.bilibili.com/x/player/playurl?type=&otype=json&fourk=1&qn=32&avid=&bvid=%s&cid=%s",
|
||||
SearchApi: "https://api.bilibili.com/x/web-interface/search/type?search_type=video&page=1&keyword=%s",
|
||||
BVRegex: regexp.MustCompile("^BV[0-9A-Za-z]+"),
|
||||
IdRegex: regexp.MustCompile("^BV[0-9A-Za-z]+(\\?p=[0-9]+)?"),
|
||||
PageRegex: regexp.MustCompile("p=[0-9]+"),
|
||||
header: map[string]string{
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0",
|
||||
"Referer": "https://www.bilibili.com/",
|
||||
"Origin": "https://www.bilibili.com",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var BilibiliVideoAPI *BilibiliVideo
|
||||
|
||||
func init() {
|
||||
BilibiliVideoAPI = _newBilibiliVideo()
|
||||
Providers[BilibiliVideoAPI.GetName()] = BilibiliVideoAPI
|
||||
}
|
||||
|
||||
func (b *BilibiliVideo) getPage(bv string) int {
|
||||
if page := b.PageRegex.FindString(bv); page != "" {
|
||||
return util.StringToInt(page[2:])
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (b *BilibiliVideo) getBv(bv string) string {
|
||||
return b.BVRegex.FindString(bv)
|
||||
}
|
||||
|
||||
func (b *BilibiliVideo) GetName() string {
|
||||
return "bilibili-video"
|
||||
}
|
||||
|
||||
func (b *BilibiliVideo) MatchMedia(keyword string) *player.Media {
|
||||
if id := b.IdRegex.FindString(keyword); id != "" {
|
||||
return &player.Media{
|
||||
Meta: Meta{
|
||||
Name: b.GetName(),
|
||||
Id: id,
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BilibiliVideo) GetPlaylist(playlist Meta) ([]*player.Media, error) {
|
||||
return nil, ErrorExternalApi
|
||||
}
|
||||
|
||||
func (b *BilibiliVideo) FormatPlaylistUrl(uri string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (b *BilibiliVideo) Search(keyword string) ([]*player.Media, error) {
|
||||
resp := httpGetString(fmt.Sprintf(b.SearchApi, url.QueryEscape(keyword)), nil)
|
||||
if resp == "" {
|
||||
return nil, ErrorExternalApi
|
||||
}
|
||||
jresp := gjson.Parse(resp)
|
||||
if jresp.Get("code").String() != "0" {
|
||||
return nil, ErrorExternalApi
|
||||
}
|
||||
result := make([]*player.Media, 0)
|
||||
r := regexp.MustCompile("</?em[^>]*>")
|
||||
jresp.Get("data.result").ForEach(func(key, value gjson.Result) bool {
|
||||
result = append(result, &player.Media{
|
||||
Title: r.ReplaceAllString(value.Get("title").String(), ""),
|
||||
Cover: player.Picture{Url: "https:" + value.Get("pic").String()},
|
||||
Artist: value.Get("author").String(),
|
||||
Meta: Meta{
|
||||
Name: b.GetName(),
|
||||
Id: value.Get("bvid").String(),
|
||||
},
|
||||
})
|
||||
return true
|
||||
})
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (b *BilibiliVideo) UpdateMedia(media *player.Media) error {
|
||||
resp := httpGetString(fmt.Sprintf(b.InfoApi, b.getBv(media.Meta.(Meta).Id)), nil)
|
||||
if resp == "" {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
jresp := gjson.Parse(resp)
|
||||
if jresp.Get("data.View.title").String() == "" {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
media.Title = jresp.Get("data.View.title").String()
|
||||
media.Artist = jresp.Get("data.View.owner.name").String()
|
||||
media.Cover.Url = jresp.Get("data.View.pic").String()
|
||||
media.Album = media.Title
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BilibiliVideo) UpdateMediaUrl(media *player.Media) error {
|
||||
resp := httpGetString(fmt.Sprintf(b.InfoApi, b.getBv(media.Meta.(Meta).Id)), nil)
|
||||
if resp == "" {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
jresp := gjson.Parse(resp)
|
||||
page := b.getPage(media.Meta.(Meta).Id) - 1
|
||||
cid := jresp.Get(fmt.Sprintf("data.View.pages.%d.cid", page)).String()
|
||||
if cid == "" {
|
||||
cid = jresp.Get("data.View.cid").String()
|
||||
}
|
||||
if cid == "" {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
resp = httpGetString(fmt.Sprintf(b.FileApi, b.getBv(media.Meta.(Meta).Id), cid), b.header)
|
||||
if resp == "" {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
jresp = gjson.Parse(resp)
|
||||
uri := jresp.Get("data.durl.0.url").String()
|
||||
if uri == "" {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
media.Url = uri
|
||||
header := make(map[string]string)
|
||||
_ = copier.Copy(&header, &b.header)
|
||||
header["Referer"] = fmt.Sprintf("https://www.bilibili.com/video/%s", b.getBv(media.Meta.(Meta).Id))
|
||||
media.Header = b.header
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BilibiliVideo) UpdateMediaLyric(media *player.Media) error {
|
||||
return nil
|
||||
}
|
||||
99
provider/bilivideo_test.go
Normal file
99
provider/bilivideo_test.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/player"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBV_GetMusicMeta(t *testing.T) {
|
||||
var api MediaProvider = BilibiliVideoAPI
|
||||
|
||||
media := player.Media{
|
||||
Meta: Meta{
|
||||
Name: api.GetName(),
|
||||
Id: "BV1434y1q71P",
|
||||
},
|
||||
}
|
||||
err := api.UpdateMedia(&media)
|
||||
fmt.Println(err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(media)
|
||||
}
|
||||
|
||||
func TestBV_GetMusic(t *testing.T) {
|
||||
var api MediaProvider = BilibiliVideoAPI
|
||||
media := player.Media{
|
||||
Meta: Meta{
|
||||
Name: api.GetName(),
|
||||
Id: "BV1434y1q71P",
|
||||
},
|
||||
}
|
||||
err := api.UpdateMedia(&media)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = api.UpdateMediaUrl(&media)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
//fmt.Println(media)
|
||||
fmt.Println(media.Url)
|
||||
}
|
||||
|
||||
func TestBV_Regex(t *testing.T) {
|
||||
fmt.Println(regexp.MustCompile("^BV[0-9A-Za-z]+(\\?p=[0-9]+)?").FindString("BV1gA411P7ir?p=3"))
|
||||
}
|
||||
|
||||
func TestBV_GetMusicMeta2(t *testing.T) {
|
||||
var api MediaProvider = BilibiliVideoAPI
|
||||
|
||||
media := player.Media{
|
||||
Meta: Meta{
|
||||
Name: api.GetName(),
|
||||
Id: "BV1gA411P7ir?p=3",
|
||||
},
|
||||
}
|
||||
err := api.UpdateMedia(&media)
|
||||
fmt.Println(err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(media)
|
||||
}
|
||||
|
||||
func TestBV_GetMusic2(t *testing.T) {
|
||||
var api MediaProvider = BilibiliVideoAPI
|
||||
media := player.Media{
|
||||
Meta: Meta{
|
||||
Name: api.GetName(),
|
||||
Id: "BV1gA411P7ir?p=3",
|
||||
},
|
||||
}
|
||||
err := api.UpdateMedia(&media)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = api.UpdateMediaUrl(&media)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
//fmt.Println(media)
|
||||
fmt.Println(media.Url)
|
||||
}
|
||||
|
||||
func TestBV_Search(t *testing.T) {
|
||||
var api MediaProvider = BilibiliVideoAPI
|
||||
result, err := api.Search("家有女友")
|
||||
if err != nil {
|
||||
fmt.Println(1, err)
|
||||
return
|
||||
}
|
||||
fmt.Println(len(result))
|
||||
for _, r := range result {
|
||||
fmt.Println(r.Artist)
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ type Kuwo struct {
|
||||
PlaylistApi string
|
||||
PlaylistRegex0 *regexp.Regexp
|
||||
PlaylistRegex1 *regexp.Regexp
|
||||
IdRegex0 *regexp.Regexp
|
||||
IdRegex1 *regexp.Regexp
|
||||
}
|
||||
|
||||
func _newKuwo() *Kuwo {
|
||||
@@ -32,6 +34,8 @@ func _newKuwo() *Kuwo {
|
||||
PlaylistApi: "http://www.kuwo.cn/api/www/playlist/playListInfo?pid=%s&pn=%d&rn=%d&httpsStatus=1",
|
||||
PlaylistRegex0: regexp.MustCompile("[0-9]+"),
|
||||
PlaylistRegex1: regexp.MustCompile("playlist/[0-9]+"),
|
||||
IdRegex0: regexp.MustCompile("^[0-9]+"),
|
||||
IdRegex1: regexp.MustCompile("^kw[0-9]+"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +50,26 @@ func (k *Kuwo) GetName() string {
|
||||
return "kuwo"
|
||||
}
|
||||
|
||||
func (k *Kuwo) MatchMedia(keyword string) *player.Media {
|
||||
if id := k.IdRegex0.FindString(keyword); id != "" {
|
||||
return &player.Media{
|
||||
Meta: Meta{
|
||||
Name: k.GetName(),
|
||||
Id: id,
|
||||
},
|
||||
}
|
||||
}
|
||||
if id := k.IdRegex1.FindString(keyword); id != "" {
|
||||
return &player.Media{
|
||||
Meta: Meta{
|
||||
Name: k.GetName(),
|
||||
Id: id[2:],
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Kuwo) FormatPlaylistUrl(uri string) string {
|
||||
var id string
|
||||
id = k.PlaylistRegex0.FindString(uri)
|
||||
@@ -92,7 +116,7 @@ func (k *Kuwo) Search(keyword string) ([]*player.Media, error) {
|
||||
gjson.Parse(resp).Get("data.list").ForEach(func(key, value gjson.Result) bool {
|
||||
result = append(result, &player.Media{
|
||||
Title: html.UnescapeString(value.Get("name").String()),
|
||||
Cover: value.Get("pic").String(),
|
||||
Cover: player.Picture{Url: value.Get("pic").String()},
|
||||
Artist: value.Get("artist").String(),
|
||||
Album: value.Get("album").String(),
|
||||
Meta: Meta{
|
||||
@@ -115,7 +139,7 @@ func (k *Kuwo) UpdateMedia(media *player.Media) error {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
media.Title = html.UnescapeString(jresp.Get("data.name").String())
|
||||
media.Cover = jresp.Get("data.pic").String()
|
||||
media.Cover.Url = jresp.Get("data.pic").String()
|
||||
media.Artist = jresp.Get("data.artist").String()
|
||||
media.Album = jresp.Get("data.album").String()
|
||||
return nil
|
||||
@@ -169,7 +193,7 @@ func (k *Kuwo) GetPlaylist(meta Meta) ([]*player.Media, error) {
|
||||
&player.Media{
|
||||
Title: html.UnescapeString(value.Get("name").String()),
|
||||
Artist: value.Get("artist").String(),
|
||||
Cover: value.Get("pic").String(),
|
||||
Cover: player.Picture{Url: value.Get("pic").String()},
|
||||
Album: value.Get("album").String(),
|
||||
Meta: Meta{
|
||||
Name: k.GetName(),
|
||||
|
||||
@@ -1,45 +1,127 @@
|
||||
package provider
|
||||
|
||||
import "AynaLivePlayer/player"
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/player"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type _LocalPlaylist struct {
|
||||
Name string
|
||||
Medias []*player.Media
|
||||
}
|
||||
|
||||
type Local struct {
|
||||
Playlists []_LocalPlaylist
|
||||
}
|
||||
|
||||
var LocalAPI *Local
|
||||
|
||||
func init() {
|
||||
LocalAPI = _newLocal()
|
||||
//Providers[LocalAPI.GetName()] = LocalAPI
|
||||
Providers[LocalAPI.GetName()] = LocalAPI
|
||||
}
|
||||
|
||||
func _newLocal() *Local {
|
||||
return &Local{}
|
||||
l := &Local{Playlists: make([]_LocalPlaylist, 0)}
|
||||
if err := os.MkdirAll(config.Provider.LocalDir, 0755); err != nil {
|
||||
return l
|
||||
}
|
||||
for _, n := range getPlaylistNames() {
|
||||
l.Playlists = append(l.Playlists, _LocalPlaylist{Name: n})
|
||||
}
|
||||
for i, _ := range l.Playlists {
|
||||
_ = readLocalPlaylist(&l.Playlists[i])
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Local) GetName() string {
|
||||
return "local"
|
||||
}
|
||||
|
||||
func (l *Local) FormatPlaylistUrl(uri string) string {
|
||||
return ""
|
||||
func (l *Local) MatchMedia(keyword string) *player.Media {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Local) GetPlaylist(playlist string) ([]*player.Media, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
func (l *Local) UpdateMediaLyric(media *player.Media) error {
|
||||
// already update in UpdateMedia, do nothing
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Local) FormatPlaylistUrl(uri string) string {
|
||||
return uri
|
||||
}
|
||||
|
||||
func (l *Local) GetPlaylist(playlist Meta) ([]*player.Media, error) {
|
||||
var pl *_LocalPlaylist = nil
|
||||
for _, p := range l.Playlists {
|
||||
if p.Name == playlist.Id {
|
||||
pl = &p
|
||||
}
|
||||
}
|
||||
if pl == nil {
|
||||
l.Playlists = append(l.Playlists, _LocalPlaylist{Name: playlist.Id})
|
||||
pl = &l.Playlists[len(l.Playlists)-1]
|
||||
}
|
||||
if readLocalPlaylist(pl) != nil {
|
||||
return nil, ErrorExternalApi
|
||||
}
|
||||
return pl.Medias, nil
|
||||
}
|
||||
|
||||
func (l *Local) Search(keyword string) ([]*player.Media, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
result := make([]struct {
|
||||
M *player.Media
|
||||
N int
|
||||
}, 0)
|
||||
keywords := strings.Split(keyword, " ")
|
||||
for _, p := range l.Playlists {
|
||||
for _, m := range p.Medias {
|
||||
n := 0
|
||||
for _, k := range keywords {
|
||||
if strings.Contains(m.Title, k) || strings.Contains(m.Artist, k) {
|
||||
n++
|
||||
}
|
||||
if k == m.Title {
|
||||
n += 2
|
||||
}
|
||||
}
|
||||
if n > 0 {
|
||||
result = append(result, struct {
|
||||
M *player.Media
|
||||
N int
|
||||
}{M: m, N: n})
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i].N > result[j].N
|
||||
})
|
||||
medias := make([]*player.Media, len(result))
|
||||
for i, r := range result {
|
||||
medias[i] = r.M.Copy()
|
||||
}
|
||||
return medias, nil
|
||||
}
|
||||
|
||||
func (l *Local) UpdateMedia(media *player.Media) error {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
mediaPath := media.Meta.(Meta).Id
|
||||
_, err := os.Stat(mediaPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return readMediaFile(media)
|
||||
}
|
||||
|
||||
func (l *Local) UpdateMediaUrl(media *player.Media) error {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
mediaPath := media.Meta.(Meta).Id
|
||||
_, err := os.Stat(mediaPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
media.Url = mediaPath
|
||||
return nil
|
||||
}
|
||||
|
||||
73
provider/local_helper.go
Normal file
73
provider/local_helper.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/player"
|
||||
"AynaLivePlayer/util"
|
||||
"github.com/dhowden/tag"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func getPlaylistNames() []string {
|
||||
names := make([]string, 0)
|
||||
items, _ := ioutil.ReadDir(config.Provider.LocalDir)
|
||||
for _, item := range items {
|
||||
if item.IsDir() {
|
||||
names = append(names, item.Name())
|
||||
}
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// readLocalPlaylist read files under a directory
|
||||
// and return a _LocalPlaylist object.
|
||||
// This function assume this directory exists
|
||||
func readLocalPlaylist(playlist *_LocalPlaylist) error {
|
||||
p1th := playlist.Name
|
||||
playlist.Medias = make([]*player.Media, 0)
|
||||
fullPath := filepath.Join(config.Provider.LocalDir, p1th)
|
||||
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
items, _ := ioutil.ReadDir(fullPath)
|
||||
for _, item := range items {
|
||||
// if item is a file, read file
|
||||
if !item.IsDir() {
|
||||
fn := item.Name()
|
||||
media := player.Media{
|
||||
Meta: Meta{
|
||||
Name: LocalAPI.GetName(),
|
||||
Id: filepath.Join(fullPath, fn),
|
||||
},
|
||||
}
|
||||
if readMediaFile(&media) != nil {
|
||||
continue
|
||||
}
|
||||
playlist.Medias = append(playlist.Medias, &media)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readMediaFile(media *player.Media) error {
|
||||
p := media.Meta.(Meta).Id
|
||||
f, err := os.Open(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
meta, err := tag.ReadFrom(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
media.Title = util.GetOrDefault(meta.Title(), filepath.Base(p))
|
||||
media.Artist = util.GetOrDefault(meta.Artist(), "Unknown")
|
||||
media.Album = util.GetOrDefault(meta.Album(), "Unknown")
|
||||
media.Lyric = meta.Lyrics()
|
||||
if meta.Picture() != nil {
|
||||
media.Cover.Data = meta.Picture().Data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
25
provider/local_test.go
Normal file
25
provider/local_test.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLocal_Read(t *testing.T) {
|
||||
items, _ := ioutil.ReadDir(".")
|
||||
for _, item := range items {
|
||||
if item.IsDir() {
|
||||
subitems, _ := ioutil.ReadDir(item.Name())
|
||||
for _, subitem := range subitems {
|
||||
if !subitem.IsDir() {
|
||||
// handle file there
|
||||
fmt.Println(item.Name() + "/" + subitem.Name())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// handle file there
|
||||
fmt.Println(item.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"AynaLivePlayer/player"
|
||||
"AynaLivePlayer/util"
|
||||
neteaseApi "github.com/XiaoMengXinX/Music163Api-Go/api"
|
||||
"github.com/XiaoMengXinX/Music163Api-Go/types"
|
||||
neteaseTypes "github.com/XiaoMengXinX/Music163Api-Go/types"
|
||||
neteaseUtil "github.com/XiaoMengXinX/Music163Api-Go/utils"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@@ -15,6 +15,9 @@ type Netease struct {
|
||||
PlaylistRegex0 *regexp.Regexp
|
||||
PlaylistRegex1 *regexp.Regexp
|
||||
ReqData neteaseUtil.RequestData
|
||||
IdRegex0 *regexp.Regexp
|
||||
IdRegex1 *regexp.Regexp
|
||||
loginStatus neteaseTypes.LoginStatusData
|
||||
}
|
||||
|
||||
func _newNetease() *Netease {
|
||||
@@ -30,6 +33,8 @@ func _newNetease() *Netease {
|
||||
},
|
||||
},
|
||||
},
|
||||
IdRegex0: regexp.MustCompile("^[0-9]+"),
|
||||
IdRegex1: regexp.MustCompile("^wy[0-9]+"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +45,9 @@ func init() {
|
||||
Providers[NeteaseAPI.GetName()] = NeteaseAPI
|
||||
}
|
||||
|
||||
func _neteaseGetArtistNames(data types.SongDetailData) string {
|
||||
// Netease private helper method
|
||||
|
||||
func _neteaseGetArtistNames(data neteaseTypes.SongDetailData) string {
|
||||
artists := make([]string, 0)
|
||||
for _, a := range data.Ar {
|
||||
artists = append(artists, a.Name)
|
||||
@@ -48,10 +55,32 @@ func _neteaseGetArtistNames(data types.SongDetailData) string {
|
||||
return strings.Join(artists, ",")
|
||||
}
|
||||
|
||||
// MediaProvider implementation
|
||||
|
||||
func (n *Netease) GetName() string {
|
||||
return "netease"
|
||||
}
|
||||
|
||||
func (n *Netease) MatchMedia(keyword string) *player.Media {
|
||||
if id := n.IdRegex0.FindString(keyword); id != "" {
|
||||
return &player.Media{
|
||||
Meta: Meta{
|
||||
Name: n.GetName(),
|
||||
Id: id,
|
||||
},
|
||||
}
|
||||
}
|
||||
if id := n.IdRegex1.FindString(keyword); id != "" {
|
||||
return &player.Media{
|
||||
Meta: Meta{
|
||||
Name: n.GetName(),
|
||||
Id: id[2:],
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Netease) FormatPlaylistUrl(uri string) string {
|
||||
var id string
|
||||
id = n.PlaylistRegex0.FindString(uri)
|
||||
@@ -96,7 +125,7 @@ func (n *Netease) GetPlaylist(meta Meta) ([]*player.Media, error) {
|
||||
medias = append(medias, &player.Media{
|
||||
Title: result2.Songs[i].Name,
|
||||
Artist: _neteaseGetArtistNames(result2.Songs[i]),
|
||||
Cover: result2.Songs[i].Al.PicUrl,
|
||||
Cover: player.Picture{Url: result2.Songs[i].Al.PicUrl},
|
||||
Album: result2.Songs[i].Al.Name,
|
||||
Url: "",
|
||||
Header: nil,
|
||||
@@ -134,7 +163,7 @@ func (n *Netease) Search(keyword string) ([]*player.Media, error) {
|
||||
medias = append(medias, &player.Media{
|
||||
Title: song.Name,
|
||||
Artist: strings.Join(artists, ","),
|
||||
Cover: "",
|
||||
Cover: player.Picture{},
|
||||
Album: song.Album.Name,
|
||||
Url: "",
|
||||
Header: nil,
|
||||
@@ -158,7 +187,7 @@ func (n *Netease) UpdateMedia(media *player.Media) error {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
media.Title = result.Songs[0].Name
|
||||
media.Cover = result.Songs[0].Al.PicUrl
|
||||
media.Cover.Url = result.Songs[0].Al.PicUrl
|
||||
media.Album = result.Songs[0].Al.Name
|
||||
media.Artist = _neteaseGetArtistNames(result.Songs[0])
|
||||
return nil
|
||||
|
||||
62
provider/netease_extra.go
Normal file
62
provider/netease_extra.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
neteaseApi "github.com/XiaoMengXinX/Music163Api-Go/api"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Netease other method
|
||||
|
||||
func (n *Netease) UpdateStatus() {
|
||||
status, _ := neteaseApi.GetLoginStatus(n.ReqData)
|
||||
n.loginStatus = status
|
||||
}
|
||||
|
||||
// IsLogin check if current cookie is a login user
|
||||
func (n *Netease) IsLogin() bool {
|
||||
return n.loginStatus.Profile.UserId != 0
|
||||
}
|
||||
|
||||
func (n *Netease) Nickname() string {
|
||||
return n.loginStatus.Profile.Nickname
|
||||
}
|
||||
|
||||
func (n *Netease) GetQrLoginKey() string {
|
||||
unikey, err := neteaseApi.GetQrUnikey(n.ReqData)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return unikey.Unikey
|
||||
}
|
||||
|
||||
func (n *Netease) GetQrLoginUrl(key string) string {
|
||||
return fmt.Sprintf("https://music.163.com/login?codekey=%s", key)
|
||||
}
|
||||
|
||||
func (n *Netease) CheckQrLogin(key string) (bool, string) {
|
||||
login, h, err := neteaseApi.CheckQrLogin(n.ReqData, key)
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
// if login.Code == 800 || login.Code == 803. login success
|
||||
if login.Code != 800 && login.Code != 803 {
|
||||
return false, login.Message
|
||||
}
|
||||
cookies := make([]*http.Cookie, 0)
|
||||
for _, c := range (&http.Response{Header: h}).Cookies() {
|
||||
if c.Name == "MUSIC_U" || c.Name == "__csrf" {
|
||||
cookies = append(cookies, c)
|
||||
}
|
||||
}
|
||||
n.ReqData.Cookies = cookies
|
||||
return true, login.Message
|
||||
}
|
||||
|
||||
func (n *Netease) Logout() {
|
||||
n.ReqData.Cookies = []*http.Cookie{
|
||||
{Name: "MUSIC_U", Value: ""},
|
||||
{Name: "__csrf", Value: ""},
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -19,6 +19,7 @@ type Meta struct {
|
||||
|
||||
type MediaProvider interface {
|
||||
GetName() string
|
||||
MatchMedia(keyword string) *player.Media
|
||||
GetPlaylist(playlist Meta) ([]*player.Media, error)
|
||||
FormatPlaylistUrl(uri string) string
|
||||
Search(keyword string) ([]*player.Media, error)
|
||||
@@ -43,6 +44,13 @@ func FormatPlaylistUrl(pname, uri string) (string, error) {
|
||||
return "", ErrorNoSuchProvider
|
||||
}
|
||||
|
||||
func MatchMedia(provider string, keyword string) *player.Media {
|
||||
if v, ok := Providers[provider]; ok {
|
||||
return v.MatchMedia(keyword)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Search(provider string, keyword string) ([]*player.Media, error) {
|
||||
if v, ok := Providers[provider]; ok {
|
||||
return v.Search(keyword)
|
||||
|
||||
20
resource/resource.go
Normal file
20
resource/resource.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
var ProgramIcon = []byte{}
|
||||
var EmptyImage = []byte{}
|
||||
|
||||
func init() {
|
||||
loadResource(config.GetAssetPath("icon.jpg"), &ProgramIcon)
|
||||
loadResource(config.GetAssetPath("empty.png"), &EmptyImage)
|
||||
}
|
||||
|
||||
func loadResource(path string, res *[]byte) {
|
||||
if file, err := ioutil.ReadFile(path); err == nil {
|
||||
*res = file
|
||||
}
|
||||
}
|
||||
31
todo.txt
31
todo.txt
@@ -5,20 +5,29 @@
|
||||
- @5 delete optimization
|
||||
|
||||
|
||||
- 文本输出
|
||||
- web输出
|
||||
- 黑名单
|
||||
- 进入beta版本
|
||||
|
||||
- web输出 (前端)
|
||||
|
||||
beta
|
||||
- web 重连
|
||||
- 黑名单
|
||||
- bilibili 歌词来源
|
||||
|
||||
----
|
||||
|
||||
Finished
|
||||
- 2022.7.01: 历史记录
|
||||
- 2022.6.29: 跳过闲置歌单
|
||||
- 2022.6.26: i18n
|
||||
- 2022.6.25: kuwo歌单
|
||||
- 2022.6.25: 设置界面
|
||||
- 2022.6.25: @6 bug, race condition, playlist size changed during playlist update.
|
||||
- 2022.6.23: 用户歌单操作
|
||||
- 2022.7.18@0.9.0: Fix bug/网页第二版跟新,加入自定义模板/修复图片加载不出来导致的闪退bug
|
||||
- 2022.7.18 : Fix bug
|
||||
- 2022.7.16@0.8.6: 网页输出第一版更新/修复历史列表部分歌曲放不出来的bug/修复部分歌词不更新
|
||||
- 2022.7.15: 更新stderr重定向/添加logo/
|
||||
- 2022.7.13@0.8.4: 网易云登录
|
||||
- 2022.7.10: Local Provider
|
||||
- 2022.7.03: 多来源点歌
|
||||
- 2022.7.01: 文本输出
|
||||
- 2022.7.01: 历史记录
|
||||
- 2022.6.29: 跳过闲置歌单
|
||||
- 2022.6.26: i18n
|
||||
- 2022.6.25: kuwo歌单
|
||||
- 2022.6.25: 设置界面
|
||||
- 2022.6.25: @6 bug, race condition, playlist size changed during playlist update.
|
||||
- 2022.6.23: 用户歌单操作
|
||||
@@ -38,3 +38,16 @@ func StringToInt(s string) int {
|
||||
i, _ := strconv.Atoi(s)
|
||||
return i
|
||||
}
|
||||
|
||||
func StringSliceCopy(src []string) []string {
|
||||
x := make([]string, len(src))
|
||||
copy(x, src)
|
||||
return x
|
||||
}
|
||||
|
||||
func GetOrDefault(s string, def string) string {
|
||||
if s == "" {
|
||||
return def
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
8
util/url.go
Normal file
8
util/url.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package util
|
||||
|
||||
import "net/url"
|
||||
|
||||
func UrlMustParse(rawurl string) *url.URL {
|
||||
u, _ := url.Parse(rawurl)
|
||||
return u
|
||||
}
|
||||
14
webtemplates.json
Normal file
14
webtemplates.json
Normal file
@@ -0,0 +1,14 @@
|
||||
[
|
||||
{
|
||||
"Name": "时间例子",
|
||||
"Template": "<p><media-title /></p>\r\n\r\n<p>秒显示:<current-time></current-time>\r\n/<total-time></total-time></p>\r\n\r\n<p>\r\n分秒显示: \r\n<current-time format=\"m:s\"/></current-time>\r\n/\r\n<total-time format=\"m:s\"/></total-time>\r\n</p>\r\n"
|
||||
},
|
||||
{
|
||||
"Name": "播放列表例子",
|
||||
"Template": "<p>默认</p>\r\n<playlist-container></playlist-container>\r\n<p>自定义格式</p>\r\n<playlist-container>\r\n <template v-slot=\"v\">\r\n <p>序号: <playlist-index :index=\"v.index\"></playlist-index></p>\r\n <p>\r\n 歌名: <playlist-title :index=\"v.index\"></playlist-title>\r\n </p>\r\n <p>\r\n 歌手: <playlist-artist :index=\"v.index\"></playlist-artist>\r\n </p>\r\n <p>\r\n 专辑名: <playlist-album :index=\"v.index\"></playlist-album>\r\n </p>\r\n <p>\r\n 点歌用户: <playlist-username :index=\"v.index\"></playlist-username>\r\n </p>\r\n </template>\r\n </playlist-container>"
|
||||
},
|
||||
{
|
||||
"Name": "default",
|
||||
"Template": "\r\n <div class=\"current-playing\">\r\n <media-title></media-title>\r\n ---\r\n <media-artist></media-artist>\r\n ---\r\n <media-username></media-username>\r\n </div>\r\n"
|
||||
}
|
||||
]
|
||||
209
前端要求.md
Normal file
209
前端要求.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# IDK what to write
|
||||
|
||||
后端已经写好了!!!
|
||||
只需要负责前端的部分就行了。
|
||||
|
||||
开源地址: [https://github.com/aynakeya/AynaLivePlayer](https://github.com/aynakeya/AynaLivePlayer)
|
||||
|
||||
|
||||
# 要求
|
||||
|
||||
- 开源项目.jpg
|
||||
- 给OBS用的,用来显示信息
|
||||
- 访问的时候先获取全部的数据渲染,然后用websocket获取更新的数据
|
||||
- 能够单个输出所有的数据 @1
|
||||
- 能自定义输出的模板(?暂定) @2
|
||||
- 最好用vue
|
||||
|
||||
### @1 单个输出所有的数据
|
||||
|
||||
类似[https://github.com/aynakeya/BiliAudioBot](https://github.com/aynakeya/BiliAudioBot)里的frontend(用vue写的),可以单个输出所有的数据。release有打包好发布的测试用例。
|
||||
|
||||
- 适合简单的输出
|
||||
- 得有一个css的class,这样子用户可以自己在obs自定义css,虽然大部分用户不知道怎么搞
|
||||
- 如果个可以的话最好能有个页面能可视化的修改css
|
||||
|
||||
比如GET `/info/CurrentTitle` 或者 `info?target=Current.Title` 返回
|
||||
```
|
||||
<p class=".current-title">外婆桥</p>
|
||||
```
|
||||
|
||||
比如 GET `info?target=Current.Cover` 返回
|
||||
```
|
||||
<img class=".current-title" src="..." />
|
||||
```
|
||||
|
||||
比如 GET `info?target=Playlist` 返回
|
||||
```
|
||||
<ol>
|
||||
<li v-for="item in playlist">
|
||||
<h2 class="playlist-info">#{{ playlist.indexOf(item) }} - {{ item.title }} - {{ item.artist }} - {{ item.username }}</h2>
|
||||
</li>
|
||||
</ol>
|
||||
```
|
||||
|
||||
### @2 自定义输出的模板
|
||||
|
||||
- **第一目标** 不整了/没想好怎么整
|
||||
- **第二目标** 最简单的还是和TextInfo一样,用户给一个模板(通过get?会不会太长了,或者本地读取?这样的话我后端获取还得加一个api来返回模板) 然后渲染出对应的html的代码,当然也要有css这样子用户可以自定义
|
||||
```
|
||||
Title: {{ .Current.Title }}
|
||||
Artist: {{ .Current.Artist }}
|
||||
Album: {{ .Current.Album}}
|
||||
Username: {{ .Current.Username }}
|
||||
Progress(in seconds): {{.CurrentTime}} / {{.TotalTime}}
|
||||
Progress(in minutes:seconds): {{ GetMinutes .CurrentTime}}:{{ GetSeconds .CurrentTime}} / {{ GetMinutes .TotalTime}}:{{ GetSeconds .TotalTime}}
|
||||
Lyric: {{.Lyric}}
|
||||
|
||||
{{range .Playlist}}
|
||||
Index: # {{ .Index}}
|
||||
Title: {{ .Title }}
|
||||
Artist: {{ .Artist }}
|
||||
Album: {{ .Album}}
|
||||
Username: {{ .Username }}
|
||||
{{end}}
|
||||
```
|
||||
- **终极目标**不知道能不能想做的像[https://github.com/xfgryujk/blivechat](https://github.com/xfgryujk/blivechat) / [https://link.bilibili.com/ctool/vtuber/index.html](https://link.bilibili.com/ctool/vtuber/index.html), 但是感觉挺麻烦的
|
||||
|
||||
|
||||
# 后端接口
|
||||
|
||||
|
||||
前端的页面在/地址下
|
||||
|
||||
两个接口
|
||||
|
||||
```
|
||||
|
||||
mux.Handle("/", http.FileServer(http.Dir(config.GetAssetPath("webinfo")))) # ./assets/webinfo
|
||||
mux.HandleFunc("/ws/info", server.handleInfo)
|
||||
mux.HandleFunc("/api/info", server.getInfo)
|
||||
|
||||
```
|
||||
|
||||
|
||||
## api详情
|
||||
|
||||
|
||||
### GET /api/info
|
||||
|
||||
**http://127.0.0.1:4000/api/info**
|
||||
|
||||
返回一个`OutInfo`, 当前的所有数据
|
||||
|
||||
```
|
||||
{
|
||||
"Current": {
|
||||
"Index": 0,
|
||||
"Title": "外婆桥",
|
||||
"Artist": "任然",
|
||||
"Album": "外婆桥",
|
||||
"Username": "System",
|
||||
"Cover": {
|
||||
"Url": "https://p1.music.126.net/Ep-CjAsRL5yvZkDreiWsMQ==/109951164390004861.jpg",
|
||||
"Data": null
|
||||
}
|
||||
},
|
||||
"CurrentTime": 6,
|
||||
"TotalTime": 261,
|
||||
"Lyric": " 编曲 : 闫津",
|
||||
"Playlist": [
|
||||
{
|
||||
"Index": 0,
|
||||
"Title": "Melody",
|
||||
"Artist": "ZIV,KIPES",
|
||||
"Album": "倒叙爱情",
|
||||
"Username": "System",
|
||||
"Cover": {
|
||||
"Url": "",
|
||||
"Data": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Index": 1,
|
||||
"Title": "Cure For Me",
|
||||
"Artist": "AURORA",
|
||||
"Album": "Cure For Me",
|
||||
"Username": "System",
|
||||
"Cover": {
|
||||
"Url": "",
|
||||
"Data": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Index": 2,
|
||||
"Title": "填满",
|
||||
"Artist": "苏星婕",
|
||||
"Album": "填满",
|
||||
"Username": "System",
|
||||
"Cover": {
|
||||
"Url": "",
|
||||
"Data": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### websocket /ws/info
|
||||
|
||||
**ws://127.0.0.1:4000/ws/info**
|
||||
|
||||
返回一个`WebsocketData`, 更新的数据
|
||||
|
||||
```
|
||||
{
|
||||
"Update": "Lyric",
|
||||
"Data": {
|
||||
"Current": {
|
||||
"Index": 0,
|
||||
"Title": "",
|
||||
"Artist": "",
|
||||
"Album": "",
|
||||
"Username": "",
|
||||
"Cover": {
|
||||
"Url": "",
|
||||
"Data": null
|
||||
}
|
||||
},
|
||||
"CurrentTime": 0,
|
||||
"TotalTime": 0,
|
||||
"Lyric": " 混音 : KIPES",
|
||||
"Playlist": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Structures
|
||||
|
||||
```
|
||||
# Url == "" && Data == nil 或者Url != "" || Data != nil
|
||||
type Picture struct {
|
||||
Url string # 如果是url就会给url
|
||||
Data []byte # 如果是二进制数据就是base64
|
||||
}
|
||||
|
||||
type MediaInfo struct {
|
||||
Index int
|
||||
Title string
|
||||
Artist string
|
||||
Album string
|
||||
Username string
|
||||
Cover player.Picture
|
||||
}
|
||||
|
||||
type OutInfo struct {
|
||||
Current MediaInfo
|
||||
CurrentTime int
|
||||
TotalTime int
|
||||
Lyric string
|
||||
Playlist []MediaInfo
|
||||
}
|
||||
|
||||
type WebsocketData struct {
|
||||
Update string
|
||||
Data OutInfo
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user