mirror of
https://github.com/AynaLivePlayer/AynaLivePlayer.git
synced 2025-12-12 21:28:11 +08:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e18ca1ff2 | ||
|
|
9ec4057412 | ||
|
|
c47d338a9e | ||
|
|
0498d2dbf3 | ||
|
|
eac8b7b775 | ||
|
|
d20c39ace3 | ||
|
|
a8d5e9d772 | ||
|
|
b6645cc575 | ||
|
|
4c0b407475 | ||
|
|
c0c83ef82a | ||
|
|
f4b080da25 | ||
|
|
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.
|
||||
60
Makefile
Normal file
60
Makefile
Normal file
@@ -0,0 +1,60 @@
|
||||
NAME = AynaLivePlayer
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
RM = del /Q /F
|
||||
RRM = rmdir /Q /S
|
||||
else
|
||||
RM = rm -f
|
||||
RRM = rm -rf
|
||||
endif
|
||||
|
||||
ifeq ($(OS), Windows_NT)
|
||||
EXECUTABLE=$(NAME).exe
|
||||
SCRIPTPATH = .\assets\scripts\windows
|
||||
else
|
||||
EXECUTABLE=$(NAME)
|
||||
SCRIPTPATH = ./assets/scripts/linux
|
||||
endif
|
||||
|
||||
gui: bundle
|
||||
go build -o $(EXECUTABLE) -ldflags -H=windowsgui ./app/gui/main.go
|
||||
|
||||
run: bundle
|
||||
go run ./app/gui/main.go
|
||||
|
||||
clear:
|
||||
$(RM) config.ini log.txt playlists.txt liverooms.json
|
||||
|
||||
bundle:
|
||||
fyne bundle --name resImageEmpty --package resource ./assets/empty.png > ./resource/bundle.go
|
||||
fyne bundle --append --name resImageIcon --package resource ./assets/icon.jpg >> ./resource/bundle.go
|
||||
fyne bundle --append --name resFontMSYaHei --package resource ./assets/msyh.ttc >> ./resource/bundle.go
|
||||
fyne bundle --append --name resFontMSYaHeiBold --package resource ./assets/msyhbd.ttc >> ./resource/bundle.go
|
||||
|
||||
release: gui
|
||||
-mkdir release
|
||||
ifeq ($(OS), Windows_NT)
|
||||
COPY .\$(EXECUTABLE) .\release\$(EXECUTABLE)
|
||||
COPY .\webtemplates.json .\release\webtemplates.json
|
||||
COPY .\assets\translation.json .\release\assets\translation.json
|
||||
COPY LICENSE.md .\release\LICENSE.md
|
||||
XCOPY .\assets\scripts\windows\* .\release\ /k /i /y /q
|
||||
XCOPY .\assets\webinfo .\release\assets\webinfo /s /e /i /y /q
|
||||
XCOPY .\music .\release\music /s /e /i /y /q
|
||||
XCOPY .\template .\release\template /s /e /i /y /q
|
||||
else
|
||||
cp ./$(EXECUTABLE) ./release/$(EXECUTABLE)
|
||||
cp ./webtemplates.json ./release/webtemplates.json
|
||||
cp ./assets/translation.json ./release/assets/translation.json
|
||||
cp LICENSE.md ./release/LICENSE.md
|
||||
cp ./assets/scripts/linux/* ./release/
|
||||
cp -r ./assets/webinfo ./release/assest/webinfo
|
||||
cp -r ./music ./release/music
|
||||
cp -r ./template ./release/template
|
||||
endif
|
||||
|
||||
clean:
|
||||
$(RM) $(EXECUTABLE) config.ini log.txt playlists.txt liverooms.json
|
||||
$(RRM) release
|
||||
|
||||
.PHONY: ${EXECUTABLE}
|
||||
@@ -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
|
||||
```
|
||||
@@ -1,23 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/controller"
|
||||
"AynaLivePlayer/logger"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Printf("BiliAudioBot Revive %s\n", config.VERSION)
|
||||
logger.Logger.SetLevel(logrus.DebugLevel)
|
||||
fmt.Println("Please enter room id")
|
||||
var roomid string
|
||||
|
||||
// Taking input from user
|
||||
fmt.Scanln(&roomid)
|
||||
controller.Initialize()
|
||||
controller.SetDanmuClient(roomid)
|
||||
ch := make(chan int)
|
||||
<-ch
|
||||
}
|
||||
@@ -240,6 +240,7 @@ This styled row should also wrap as expected, but only *when required*.
|
||||
grid := makeTextGrid()
|
||||
return container.NewBorder(fixed, grid, nil, nil,
|
||||
container.NewGridWithRows(2, rich, entryLoremIpsum))
|
||||
|
||||
}
|
||||
|
||||
func makeInputTab(_ fyne.Window) fyne.CanvasObject {
|
||||
|
||||
@@ -1,41 +1,51 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/i18n"
|
||||
"AynaLivePlayer/common/logger"
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/controller"
|
||||
"AynaLivePlayer/controller/core"
|
||||
"AynaLivePlayer/gui"
|
||||
"AynaLivePlayer/i18n"
|
||||
"AynaLivePlayer/logger"
|
||||
"AynaLivePlayer/player"
|
||||
"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"
|
||||
"flag"
|
||||
)
|
||||
|
||||
func init() {
|
||||
exitStatus, _ := panicwrap.BasicWrap(func(s string) {
|
||||
logger.Logger.Panic(s)
|
||||
os.Exit(1)
|
||||
return
|
||||
})
|
||||
if exitStatus >= 0 {
|
||||
os.Exit(exitStatus)
|
||||
}
|
||||
var dev = flag.Bool("dev", false, "generate new translation file")
|
||||
|
||||
func createController() controller.IController {
|
||||
liveroom := core.NewLiveRoomController()
|
||||
lyric := core.NewLyricLoader()
|
||||
provider := core.NewProviderController()
|
||||
playlist := core.NewPlaylistController(provider)
|
||||
plugin := core.NewPluginController()
|
||||
mpvPlayer := player.NewMpvPlayer()
|
||||
playControl := core.NewPlayerController(mpvPlayer, playlist, lyric, provider)
|
||||
ctr := core.NewController(liveroom, playControl, playlist, provider, plugin)
|
||||
return ctr
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Printf("BiliAudioBot Revive %s\n", config.VERSION)
|
||||
logger.Logger.SetLevel(logrus.DebugLevel)
|
||||
controller.Initialize()
|
||||
controller.LoadPlugins(diange.NewDiange(), qiege.NewQiege(), textinfo.NewTextInfo())
|
||||
defer func() {
|
||||
controller.Destroy()
|
||||
config.SaveToConfigFile(config.CONFIG_PATH)
|
||||
i18n.SaveTranslation()
|
||||
}()
|
||||
flag.Parse()
|
||||
logger.Logger.Info("================Program Start================")
|
||||
logger.Logger.Infof("================Current Version: %s================", config.Version)
|
||||
mainController := createController()
|
||||
controller.Instance = mainController
|
||||
gui.Initialize()
|
||||
plugins := []controller.Plugin{diange.NewDiange(mainController), qiege.NewQiege(mainController),
|
||||
textinfo.NewTextInfo(mainController), webinfo.NewWebInfo(mainController),
|
||||
wylogin.NewWYLogin()}
|
||||
mainController.LoadPlugins(plugins...)
|
||||
gui.MainWindow.ShowAndRun()
|
||||
mainController.CloseAndSave()
|
||||
if *dev {
|
||||
i18n.SaveTranslation()
|
||||
}
|
||||
_ = config.SaveToConfigFile(config.ConfigPath)
|
||||
logger.Logger.Info("================Program End================")
|
||||
}
|
||||
|
||||
27
app/wrapwordbug/main.go
Normal file
27
app/wrapwordbug/main.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/app"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := app.New()
|
||||
w := a.NewWindow("Hello World")
|
||||
|
||||
texts := make([]fyne.CanvasObject, 1)
|
||||
for i := 0; i < len(texts); i++ {
|
||||
l := widget.NewLabelWithStyle(
|
||||
" AAAA",
|
||||
fyne.TextAlignCenter, fyne.TextStyle{})
|
||||
l.Wrapping = fyne.TextWrapWord
|
||||
texts[i] = l
|
||||
}
|
||||
vbox := container.NewVBox(texts...)
|
||||
scroll := container.NewScroll(vbox)
|
||||
w.SetContent(scroll)
|
||||
w.Resize(fyne.NewSize(360, 540))
|
||||
w.ShowAndRun()
|
||||
}
|
||||
@@ -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 |
BIN
assets/msyhbd.ttc
Normal file
BIN
assets/msyhbd.ttc
Normal file
Binary file not shown.
1
assets/scripts/windows/强制结束-出现任何奇怪问题点我重置.bat
Normal file
1
assets/scripts/windows/强制结束-出现任何奇怪问题点我重置.bat
Normal file
@@ -0,0 +1 @@
|
||||
taskkill /IM "AynaLivePlayer.exe" /F
|
||||
1
assets/scripts/windows/重置winsock.bat
Normal file
1
assets/scripts/windows/重置winsock.bat
Normal file
@@ -0,0 +1 @@
|
||||
netsh winsock reset
|
||||
@@ -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": "添加歌单"
|
||||
@@ -124,6 +128,30 @@
|
||||
"en": "Current: None",
|
||||
"zh-CN": "当前为: 无"
|
||||
},
|
||||
"gui.room.add.cancel": {
|
||||
"en": "Cancel",
|
||||
"zh-CN": "取消"
|
||||
},
|
||||
"gui.room.add.client_name": {
|
||||
"en": "Client Name",
|
||||
"zh-CN": "直播平台"
|
||||
},
|
||||
"gui.room.add.confirm": {
|
||||
"en": "Confirm",
|
||||
"zh-CN": "确定"
|
||||
},
|
||||
"gui.room.add.id_url": {
|
||||
"en": "Room ID",
|
||||
"zh-CN": "房间号"
|
||||
},
|
||||
"gui.room.add.prompt": {
|
||||
"en": "enter room id",
|
||||
"zh-CN": "填入房间号"
|
||||
},
|
||||
"gui.room.add.title": {
|
||||
"en": "Add Room",
|
||||
"zh-CN": "添加房间"
|
||||
},
|
||||
"gui.room.btn.connect": {
|
||||
"en": "Connect",
|
||||
"zh-CN": "连接"
|
||||
@@ -132,6 +160,18 @@
|
||||
"en": "Disconnect",
|
||||
"zh-CN": "断开"
|
||||
},
|
||||
"gui.room.button.add": {
|
||||
"en": "Add",
|
||||
"zh-CN": "新增"
|
||||
},
|
||||
"gui.room.button.remove": {
|
||||
"en": "Remove",
|
||||
"zh-CN": "删除"
|
||||
},
|
||||
"gui.room.check.autoconnect": {
|
||||
"en": "Auto Connection",
|
||||
"zh-CN": "自动连接"
|
||||
},
|
||||
"gui.room.id": {
|
||||
"en": "Room ID: ",
|
||||
"zh-CN": "房间号: "
|
||||
@@ -220,6 +260,18 @@
|
||||
"en": "Basic Diange Configuration",
|
||||
"zh-CN": "点歌基本设置"
|
||||
},
|
||||
"plugin.diange.medal.level": {
|
||||
"en": "Level",
|
||||
"zh-CN": "等级"
|
||||
},
|
||||
"plugin.diange.medal.name": {
|
||||
"en": "Name",
|
||||
"zh-CN": "牌子名"
|
||||
},
|
||||
"plugin.diange.medal.perm": {
|
||||
"en": "Medal Permission",
|
||||
"zh-CN": "牌子点歌权限"
|
||||
},
|
||||
"plugin.diange.permission": {
|
||||
"en": "Permission",
|
||||
"zh-CN": "点歌权限"
|
||||
@@ -232,6 +284,10 @@
|
||||
"en": "Max Queue",
|
||||
"zh-CN": "最大点歌数"
|
||||
},
|
||||
"plugin.diange.source_cmd": {
|
||||
"en": "Source Command",
|
||||
"zh-CN": "来源点歌命令"
|
||||
},
|
||||
"plugin.diange.title": {
|
||||
"en": "Diange",
|
||||
"zh-CN": "点歌"
|
||||
@@ -240,6 +296,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 +371,54 @@
|
||||
"plugin.textinfo.title": {
|
||||
"en": "Text Output",
|
||||
"zh-CN": "文本输出"
|
||||
},
|
||||
"plugin.webinfo.autostart": {
|
||||
"en": "Auto start",
|
||||
"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_preview": {
|
||||
"en": "Server Preview",
|
||||
"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输出"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0
assets/webinfo/.gitkeep
Normal file
0
assets/webinfo/.gitkeep
Normal file
126
common/event/event.go
Normal file
126
common/event/event.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type EventId string
|
||||
|
||||
type Event struct {
|
||||
Id EventId
|
||||
Cancelled bool
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
type HandlerFunc func(event *Event)
|
||||
|
||||
type Handler struct {
|
||||
EventId EventId
|
||||
Name string
|
||||
Handler HandlerFunc
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
handlers map[EventId]map[string]*Handler
|
||||
queue chan func()
|
||||
stopSig chan int
|
||||
queueSize int
|
||||
workerSize int
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func NewManger(queueSize int, workerSize int) *Manager {
|
||||
manager := &Manager{
|
||||
handlers: make(map[EventId]map[string]*Handler),
|
||||
queue: make(chan func(), queueSize),
|
||||
stopSig: make(chan int, workerSize),
|
||||
queueSize: queueSize,
|
||||
workerSize: workerSize,
|
||||
}
|
||||
for i := 0; i < workerSize; i++ {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-manager.stopSig:
|
||||
return
|
||||
case f := <-manager.queue:
|
||||
f()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
return manager
|
||||
}
|
||||
|
||||
func (h *Manager) NewChildManager() *Manager {
|
||||
return &Manager{
|
||||
handlers: make(map[EventId]map[string]*Handler),
|
||||
queue: h.queue,
|
||||
stopSig: h.stopSig,
|
||||
queueSize: h.queueSize,
|
||||
workerSize: h.workerSize,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Manager) Stop() {
|
||||
for i := 0; i < h.workerSize; i++ {
|
||||
h.stopSig <- 0
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Manager) Register(handler *Handler) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
m, ok := h.handlers[handler.EventId]
|
||||
if !ok {
|
||||
m = make(map[string]*Handler)
|
||||
h.handlers[handler.EventId] = m
|
||||
}
|
||||
m[handler.Name] = handler
|
||||
}
|
||||
|
||||
func (h *Manager) RegisterA(id EventId, name string, handler HandlerFunc) {
|
||||
h.Register(&Handler{
|
||||
EventId: id,
|
||||
Name: name,
|
||||
Handler: handler,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Manager) UnregisterAll() {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
h.handlers = make(map[EventId]map[string]*Handler)
|
||||
}
|
||||
|
||||
func (h *Manager) Unregister(name string) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
for _, m := range h.handlers {
|
||||
if _, ok := m[name]; ok {
|
||||
delete(m, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Manager) Call(event *Event) {
|
||||
h.lock.RLock()
|
||||
handlers, ok := h.handlers[event.Id]
|
||||
h.lock.RUnlock()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for _, eh := range handlers {
|
||||
handler := eh.Handler
|
||||
h.queue <- func() {
|
||||
handler(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Manager) CallA(id EventId, data interface{}) {
|
||||
h.Call(&Event{
|
||||
Id: id,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
10
common/event/pool.go
Normal file
10
common/event/pool.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package event
|
||||
|
||||
var MAX_QUEUE_SIZE = 128
|
||||
var MAX_WORKER_SIZE = 16
|
||||
|
||||
var MainManager *Manager
|
||||
|
||||
func init() {
|
||||
MainManager = NewManger(MAX_QUEUE_SIZE, MAX_WORKER_SIZE)
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package i18n
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/util"
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/util"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
const FILENAME = "translation.json"
|
||||
@@ -28,7 +28,7 @@ var CurrentLanguage string
|
||||
|
||||
func init() {
|
||||
TranslationMap = Translation{make([]string, 0), make(map[string]map[string]string)}
|
||||
file, err := ioutil.ReadFile(config.GetAssetPath(FILENAME))
|
||||
file, err := os.ReadFile(config.GetAssetPath(FILENAME))
|
||||
if err == nil {
|
||||
_ = json.Unmarshal([]byte(file), &TranslationMap)
|
||||
}
|
||||
@@ -59,5 +59,5 @@ func T(id string) string {
|
||||
|
||||
func SaveTranslation() {
|
||||
content, _ := util.MarshalIndentUnescape(TranslationMap, "", " ")
|
||||
_ = ioutil.WriteFile(config.GetAssetPath(FILENAME), []byte(content), 0666)
|
||||
_ = os.WriteFile(config.GetAssetPath(FILENAME), []byte(content), 0666)
|
||||
}
|
||||
@@ -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,22 @@ var Logger *logrus.Logger
|
||||
func init() {
|
||||
Logger = logrus.New()
|
||||
Logger.SetLevel(config.Log.Level)
|
||||
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,
|
||||
})
|
||||
_ = os.Truncate(config.Log.Path, 0)
|
||||
file, err := os.OpenFile(config.Log.Path, os.O_CREATE|os.O_WRONLY, 0666)
|
||||
if err == nil {
|
||||
Logger.Out = io.MultiWriter(file, os.Stdout)
|
||||
} else {
|
||||
Logger.Info("Failed to log to file, using default stdout")
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
69
common/util/generic.go
Normal file
69
common/util/generic.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
func Slice[T any](arr []T, from int, to int) []T {
|
||||
l := len(arr)
|
||||
to = Min(to, l)
|
||||
from = Min(from, l)
|
||||
if to <= 0 {
|
||||
to = l + to
|
||||
if to <= 0 {
|
||||
return []T{}
|
||||
}
|
||||
}
|
||||
if from < 0 {
|
||||
from = l + from
|
||||
if from < 0 {
|
||||
from = 0
|
||||
}
|
||||
}
|
||||
if to <= from {
|
||||
return []T{}
|
||||
}
|
||||
return arr[from:to]
|
||||
}
|
||||
|
||||
func SliceCopy[T any](src []T) []T {
|
||||
x := make([]T, len(src))
|
||||
copy(x, src)
|
||||
return x
|
||||
}
|
||||
|
||||
func SliceContains[T comparable](s []T, e T) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TernaryOp[T any](cond bool, a T, b T) T {
|
||||
if cond {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func Min[T constraints.Ordered](arr ...T) T {
|
||||
min := arr[0]
|
||||
for _, a := range arr {
|
||||
if a < min {
|
||||
min = a
|
||||
}
|
||||
}
|
||||
return min
|
||||
}
|
||||
|
||||
func Max[T constraints.Ordered](arr ...T) T {
|
||||
max := arr[0]
|
||||
for _, a := range arr {
|
||||
if a > max {
|
||||
max = a
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
119
common/util/string.go
Normal file
119
common/util/string.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func StrLen(str string) int {
|
||||
return len([]rune(str))
|
||||
}
|
||||
|
||||
func StringNormalize(str string, min int, max int) string {
|
||||
fmtStr := fmt.Sprintf("%%-%d.%ds", min, max)
|
||||
return fmt.Sprintf(fmtStr, str)
|
||||
}
|
||||
|
||||
func Atoi(s string) int {
|
||||
i, _ := strconv.Atoi(s)
|
||||
return i
|
||||
}
|
||||
|
||||
func GetOrDefault(s string, def string) string {
|
||||
if s == "" {
|
||||
return def
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func LevenshteinDistance(s1 string, s2 string) int {
|
||||
// support unicode
|
||||
r1 := []rune(s1)
|
||||
r2 := []rune(s2)
|
||||
r1l := len(r1)
|
||||
r2l := len(r2)
|
||||
if r1l == 0 || r2l == 0 {
|
||||
return Max(r1l, r2l)
|
||||
}
|
||||
previous := make([]int, r2l+1)
|
||||
current := make([]int, r2l+1)
|
||||
|
||||
for i := 0; i <= r2l; i++ {
|
||||
previous[i] = i
|
||||
}
|
||||
|
||||
for i := 1; i <= r1l; i++ {
|
||||
current[0] = i
|
||||
for j := 1; j <= r2l; j++ {
|
||||
subCost := 1
|
||||
if r1[i-1] == r2[j-1] {
|
||||
subCost = 0
|
||||
}
|
||||
// current[j] = min( insertCost,deleteCost, subCost)
|
||||
current[j] = Min(current[j-1]+1, previous[j]+1, previous[j-1]+subCost)
|
||||
}
|
||||
current, previous = previous, current
|
||||
}
|
||||
return previous[r2l]
|
||||
}
|
||||
|
||||
func WeightedLevenshteinDistance(s1 string, s2 string, ins, del, repl int) int {
|
||||
// support unicode
|
||||
r1 := []rune(s1)
|
||||
r2 := []rune(s2)
|
||||
r1l := len(r1)
|
||||
r2l := len(r2)
|
||||
if r1l == 0 || r2l == 0 {
|
||||
return Max(r1l, r2l)
|
||||
}
|
||||
previous := make([]int, r2l+1)
|
||||
current := make([]int, r2l+1)
|
||||
|
||||
for i := 0; i <= r2l; i++ {
|
||||
previous[i] = i
|
||||
}
|
||||
|
||||
for i := 1; i <= r1l; i++ {
|
||||
current[0] = i
|
||||
for j := 1; j <= r2l; j++ {
|
||||
subCost := 1
|
||||
if r1[i-1] == r2[j-1] {
|
||||
subCost = 0
|
||||
}
|
||||
// current[j] = min( insertCost,deleteCost, subCost)
|
||||
current[j] = Min(current[j-1]+1*ins, previous[j]+1*del, previous[j-1]+subCost*repl)
|
||||
}
|
||||
current, previous = previous, current
|
||||
}
|
||||
return previous[r2l]
|
||||
}
|
||||
|
||||
func LongestCommonString(s1 string, s2 string) string {
|
||||
// support unicode
|
||||
r1 := []rune(s1)
|
||||
r2 := []rune(s2)
|
||||
r1l := len(r1)
|
||||
r2l := len(r2)
|
||||
if r1l == 0 || r2l == 0 {
|
||||
return ""
|
||||
}
|
||||
previous := make([]int, r2l+1)
|
||||
current := make([]int, r2l+1)
|
||||
max := 0
|
||||
maxIndex := 0
|
||||
for i := 1; i <= r1l; i++ {
|
||||
for j := 1; j <= r2l; j++ {
|
||||
if r1[i-1] == r2[j-1] {
|
||||
current[j] = previous[j-1] + 1
|
||||
if current[j] > max {
|
||||
max = current[j]
|
||||
maxIndex = i
|
||||
}
|
||||
} else {
|
||||
current[j] = 0
|
||||
}
|
||||
}
|
||||
current, previous = previous, current
|
||||
}
|
||||
return string(r1[maxIndex-max : maxIndex])
|
||||
}
|
||||
32
common/util/string_test.go
Normal file
32
common/util/string_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLevenshteinDistance(t *testing.T) {
|
||||
assert.Equal(t, 3, LevenshteinDistance("kitten", "sitting"))
|
||||
assert.Equal(t, 0, LevenshteinDistance("kitten", "kitten"))
|
||||
assert.Equal(t, 1, LevenshteinDistance("kitten", "kittens"))
|
||||
assert.Equal(t, 2, LevenshteinDistance("kitten", "kitt"))
|
||||
assert.Greater(t, LevenshteinDistance("夜曲 周杰伦/方文山", "夜曲 周杰伦"), LevenshteinDistance("夜曲 翻唱A", "夜曲 周杰伦"))
|
||||
assert.Greater(t,
|
||||
WeightedLevenshteinDistance("Mojito Tommy Hong", "Mojito 周杰伦", 1, 1, 3),
|
||||
WeightedLevenshteinDistance("Mojito 周杰伦", "Mojito 周杰伦", 1, 1, 3))
|
||||
assert.Greater(t,
|
||||
WeightedLevenshteinDistance("默 (Live) 李荣浩/周杰伦", "Mojito 周杰伦", 1, 1, 3),
|
||||
WeightedLevenshteinDistance("Mojito 周杰伦", "Mojito 周杰伦", 1, 1, 3))
|
||||
assert.Greater(t,
|
||||
WeightedLevenshteinDistance("布拉格广场 周杰伦", "Mojito 周杰伦", 1, 1, 3),
|
||||
WeightedLevenshteinDistance("Mojito 周杰伦", "Mojito 周杰伦", 1, 1, 3))
|
||||
assert.Greater(t,
|
||||
WeightedLevenshteinDistance("Mojito(翻自 cover 周杰伦)野猪佩奇", "Mojito 周杰伦", 1, 1, 3),
|
||||
WeightedLevenshteinDistance("Mojito 周杰伦", "Mojito 周杰伦", 1, 1, 3))
|
||||
//assert.Less(t, WeightedLevenshteinDistance("夜曲 周杰伦/方文山", "夜曲 周杰伦",1,1,3), WeightedLevenshteinDistance("夜曲 翻唱A", "夜曲 周杰伦",1,1,3))
|
||||
}
|
||||
|
||||
func TestLongestCommonString(t *testing.T) {
|
||||
assert.Equal(t, "itt", LongestCommonString("kitten", "sitting"))
|
||||
assert.Equal(t, "布拉格广场", LongestCommonString("布拉格广场 周杰伦", "布拉格广场"))
|
||||
}
|
||||
8
common/util/url.go
Normal file
8
common/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
|
||||
}
|
||||
@@ -6,17 +6,33 @@ import (
|
||||
"path"
|
||||
)
|
||||
|
||||
const VERSION = "alpha 0.7.0"
|
||||
const (
|
||||
ProgramName = "卡西米尔唱片机"
|
||||
Version = "beta 0.9.5"
|
||||
)
|
||||
|
||||
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 {
|
||||
Name() string
|
||||
OnLoad()
|
||||
OnSave()
|
||||
}
|
||||
|
||||
type BaseConfig struct {
|
||||
}
|
||||
|
||||
func (c *BaseConfig) OnLoad() {
|
||||
}
|
||||
|
||||
func (c *BaseConfig) OnSave() {
|
||||
}
|
||||
|
||||
var ConfigFile *ini.File
|
||||
@@ -27,18 +43,19 @@ func LoadConfig(cfg Config) {
|
||||
if err == nil {
|
||||
_ = sec.MapTo(cfg)
|
||||
}
|
||||
cfg.OnLoad()
|
||||
Configs = append(Configs, cfg)
|
||||
return
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
for _, cfg := range []Config{Log, LiveRoom, Player, Provider, General} {
|
||||
for _, cfg := range []Config{Log, General} {
|
||||
LoadConfig(cfg)
|
||||
}
|
||||
}
|
||||
@@ -46,6 +63,7 @@ func init() {
|
||||
func SaveToConfigFile(filename string) error {
|
||||
cfgFile := ini.Empty()
|
||||
for _, cfg := range Configs {
|
||||
cfg.OnSave()
|
||||
if err := cfgFile.Section(cfg.Name()).ReflectFrom(cfg); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
type _GeneralConfig struct {
|
||||
BaseConfig
|
||||
Language string
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package config
|
||||
|
||||
type _LiveRoomConfig struct {
|
||||
History []string
|
||||
}
|
||||
|
||||
func (c *_LiveRoomConfig) Name() string {
|
||||
return "LiveRoom"
|
||||
}
|
||||
|
||||
var LiveRoom = &_LiveRoomConfig{History: []string{"9076804", "3819533"}}
|
||||
@@ -3,8 +3,15 @@ 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) OnLoad() {
|
||||
}
|
||||
|
||||
func (c *_LogConfig) OnSave() {
|
||||
}
|
||||
|
||||
func (c *_LogConfig) Name() string {
|
||||
@@ -12,6 +19,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.
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
package config
|
||||
|
||||
type _PlayerConfig struct {
|
||||
Playlists []string
|
||||
PlaylistsProvider []string
|
||||
PlaylistIndex int
|
||||
PlaylistRandom bool
|
||||
AudioDevice string
|
||||
Volume float64
|
||||
SkipPlaylist bool
|
||||
}
|
||||
|
||||
func (c *_PlayerConfig) Name() string {
|
||||
return "Player"
|
||||
}
|
||||
|
||||
var Player = &_PlayerConfig{
|
||||
Playlists: []string{"2382819181", "4987059624", "646548465"},
|
||||
PlaylistsProvider: []string{"netease", "netease", "netease"},
|
||||
PlaylistIndex: 0,
|
||||
PlaylistRandom: true,
|
||||
AudioDevice: "auto",
|
||||
Volume: 100,
|
||||
SkipPlaylist: false,
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package config
|
||||
|
||||
type _ProviderConfig struct {
|
||||
Priority []string
|
||||
LocalDir string
|
||||
}
|
||||
|
||||
func (c *_ProviderConfig) Name() string {
|
||||
return "Provider"
|
||||
}
|
||||
|
||||
var Provider = &_ProviderConfig{
|
||||
Priority: []string{"local", "netease", "kuwo", "bilibili"},
|
||||
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) {
|
||||
|
||||
23
config/jsonconfig.go
Normal file
23
config/jsonconfig.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
)
|
||||
|
||||
func LoadJson(path string, dst any) error {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(data, dst)
|
||||
}
|
||||
|
||||
func SaveJson(path string, dst any) error {
|
||||
data, err := json.MarshalIndent(dst, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(path, data, 0666)
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/event"
|
||||
"AynaLivePlayer/liveclient"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var Commands []DanmuCommandExecutor
|
||||
|
||||
type DanmuCommandExecutor interface {
|
||||
Match(command string) bool
|
||||
Execute(command string, args []string, danmu *liveclient.DanmuMessage)
|
||||
}
|
||||
|
||||
func AddCommand(executors ...DanmuCommandExecutor) {
|
||||
Commands = append(Commands, executors...)
|
||||
}
|
||||
|
||||
func danmuCommandHandler(event *event.Event) {
|
||||
danmu := event.Data.(*liveclient.DanmuMessage)
|
||||
args := strings.Split(danmu.Message, " ")
|
||||
if len(args[0]) == 0 {
|
||||
return
|
||||
}
|
||||
for _, cmd := range Commands {
|
||||
if cmd.Match(args[0]) {
|
||||
cmd.Execute(args[0], args[1:], danmu)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,122 +1,13 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/event"
|
||||
"AynaLivePlayer/liveclient"
|
||||
"AynaLivePlayer/logger"
|
||||
"AynaLivePlayer/player"
|
||||
"AynaLivePlayer/provider"
|
||||
"AynaLivePlayer/util"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"strconv"
|
||||
)
|
||||
var Instance IController = nil
|
||||
|
||||
const MODULE_CONTROLLER = "Controller"
|
||||
|
||||
func l() *logrus.Entry {
|
||||
return logger.Logger.WithField("Module", MODULE_CONTROLLER)
|
||||
}
|
||||
|
||||
func SetDanmuClient(roomId string) {
|
||||
ResetDanmuClient()
|
||||
l().Infof("setting live client for %s", roomId)
|
||||
room, err := strconv.Atoi(roomId)
|
||||
if err != nil {
|
||||
l().Warn("parse room id error", err)
|
||||
return
|
||||
}
|
||||
if !util.StringSliceContains(config.LiveRoom.History, roomId) {
|
||||
config.LiveRoom.History = append(config.LiveRoom.History, roomId)
|
||||
}
|
||||
LiveClient = liveclient.NewBilibili(room)
|
||||
LiveClient.Handler().Register(&event.EventHandler{
|
||||
EventId: liveclient.EventMessageReceive,
|
||||
Name: "controller.commandexecutor",
|
||||
Handler: danmuCommandHandler,
|
||||
})
|
||||
LiveClient.Handler().RegisterA(
|
||||
liveclient.EventMessageReceive,
|
||||
"controller.danmu.handler",
|
||||
danmuHandler)
|
||||
l().Infof("setting live client for %s success", roomId)
|
||||
}
|
||||
|
||||
func StartDanmuClient() {
|
||||
LiveClient.Connect()
|
||||
}
|
||||
|
||||
func ResetDanmuClient() {
|
||||
if LiveClient != nil {
|
||||
l().Infof("disconnect from current live client %s", LiveClient.ClientName())
|
||||
LiveClient.Disconnect()
|
||||
LiveClient.Handler().UnregisterAll()
|
||||
LiveClient = nil
|
||||
}
|
||||
}
|
||||
|
||||
func AddPlaylist(pname string, uri string) *player.Playlist {
|
||||
l().Infof("try add playlist %s with provider %s", uri, pname)
|
||||
id, err := provider.FormatPlaylistUrl(pname, uri)
|
||||
if err != nil || id == "" {
|
||||
l().Warnf("fail to format %s playlist id for %s", uri, pname)
|
||||
return nil
|
||||
}
|
||||
p := player.NewPlaylist(fmt.Sprintf("%s-%s", pname, id), player.PlaylistConfig{})
|
||||
p.Meta = provider.Meta{
|
||||
Name: pname,
|
||||
Id: id,
|
||||
}
|
||||
PlaylistManager = append(PlaylistManager, p)
|
||||
config.Player.Playlists = append(config.Player.Playlists, id)
|
||||
config.Player.PlaylistsProvider = append(config.Player.PlaylistsProvider, pname)
|
||||
return p
|
||||
}
|
||||
|
||||
func RemovePlaylist(index int) {
|
||||
l().Infof("Try to remove playlist.index=%d", index)
|
||||
if index < 0 || index >= len(PlaylistManager) {
|
||||
l().Warnf("playlist.index=%d not found", index)
|
||||
return
|
||||
}
|
||||
if index == config.Player.PlaylistIndex {
|
||||
l().Info("Delete current system playlist, reset system playlist to index = 0")
|
||||
SetSystemPlaylist(0)
|
||||
}
|
||||
if index < config.Player.PlaylistIndex {
|
||||
l().Debugf("Delete playlist before system playlist (index=%d), reduce system playlist index by 1", config.Player.PlaylistIndex)
|
||||
config.Player.PlaylistIndex = config.Player.PlaylistIndex - 1
|
||||
}
|
||||
PlaylistManager = append(PlaylistManager[:index], PlaylistManager[index+1:]...)
|
||||
config.Player.Playlists = append(config.Player.Playlists[:index], config.Player.Playlists[index+1:]...)
|
||||
config.Player.PlaylistsProvider = append(config.Player.PlaylistsProvider[:index], config.Player.PlaylistsProvider[index+1:]...)
|
||||
}
|
||||
|
||||
func SetSystemPlaylist(index int) {
|
||||
l().Infof("try set system playlist to playlist.id=%d", index)
|
||||
if index < 0 || index >= len(PlaylistManager) {
|
||||
l().Warn("playlist.index=%d not found", index)
|
||||
return
|
||||
}
|
||||
err := PreparePlaylist(PlaylistManager[index])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
medias := PlaylistManager[index].Playlist
|
||||
config.Player.PlaylistIndex = index
|
||||
ApplyUser(medias, player.PlaylistUser)
|
||||
SystemPlaylist.Replace(medias)
|
||||
}
|
||||
|
||||
func PreparePlaylistByIndex(index int) {
|
||||
l().Infof("try prepare playlist.id=%d", index)
|
||||
if index < 0 || index >= len(PlaylistManager) {
|
||||
l().Warn("playlist.id=%d not found", index)
|
||||
return
|
||||
}
|
||||
err := PreparePlaylist(PlaylistManager[index])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
type IController interface {
|
||||
LiveRooms() ILiveRoomController
|
||||
PlayControl() IPlayController
|
||||
Playlists() IPlaylistController
|
||||
Provider() IProviderController
|
||||
Plugin() IPluginController
|
||||
LoadPlugins(plugins ...Plugin)
|
||||
CloseAndSave()
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestController(t *testing.T) {
|
||||
fmt.Println(LiveClient == nil)
|
||||
}
|
||||
59
controller/core/controller.go
Normal file
59
controller/core/controller.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/logger"
|
||||
"AynaLivePlayer/controller"
|
||||
)
|
||||
|
||||
var lg = logger.Logger.WithField("Module", "CoreController")
|
||||
|
||||
type Controller struct {
|
||||
liveroom controller.ILiveRoomController `ini:"-"`
|
||||
player controller.IPlayController `ini:"-"`
|
||||
lyric controller.ILyricLoader `ini:"-"`
|
||||
playlist controller.IPlaylistController `ini:"-"`
|
||||
provider controller.IProviderController `ini:"-"`
|
||||
plugin controller.IPluginController `ini:"-"`
|
||||
}
|
||||
|
||||
func NewController(
|
||||
liveroom controller.ILiveRoomController, player controller.IPlayController,
|
||||
playlist controller.IPlaylistController,
|
||||
provider controller.IProviderController, plugin controller.IPluginController) controller.IController {
|
||||
cc := &Controller{
|
||||
liveroom: liveroom,
|
||||
player: player,
|
||||
playlist: playlist,
|
||||
provider: provider,
|
||||
plugin: plugin,
|
||||
}
|
||||
return cc
|
||||
}
|
||||
|
||||
func (c *Controller) LiveRooms() controller.ILiveRoomController {
|
||||
return c.liveroom
|
||||
}
|
||||
|
||||
func (c *Controller) PlayControl() controller.IPlayController {
|
||||
return c.player
|
||||
}
|
||||
|
||||
func (c *Controller) Playlists() controller.IPlaylistController {
|
||||
return c.playlist
|
||||
}
|
||||
|
||||
func (c *Controller) Provider() controller.IProviderController {
|
||||
return c.provider
|
||||
}
|
||||
|
||||
func (c *Controller) Plugin() controller.IPluginController {
|
||||
return c.plugin
|
||||
}
|
||||
|
||||
func (c *Controller) LoadPlugins(plugins ...controller.Plugin) {
|
||||
c.plugin.LoadPlugins(plugins...)
|
||||
}
|
||||
|
||||
func (c *Controller) CloseAndSave() {
|
||||
c.plugin.ClosePlugins()
|
||||
}
|
||||
201
controller/core/liveroom.go
Normal file
201
controller/core/liveroom.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/controller"
|
||||
"AynaLivePlayer/liveclient"
|
||||
"AynaLivePlayer/model"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type coreLiveRoom struct {
|
||||
model.LiveRoom
|
||||
client liveclient.LiveClient
|
||||
}
|
||||
|
||||
func (r *coreLiveRoom) Model() *model.LiveRoom {
|
||||
return &r.LiveRoom
|
||||
}
|
||||
|
||||
func (r *coreLiveRoom) Status() bool {
|
||||
return r.client.Status()
|
||||
}
|
||||
|
||||
func (r *coreLiveRoom) EventManager() *event.Manager {
|
||||
return r.client.EventManager()
|
||||
}
|
||||
|
||||
func (r *coreLiveRoom) init(msgHandler event.HandlerFunc) (err error) {
|
||||
if r.client != nil {
|
||||
return nil
|
||||
}
|
||||
r.client, err = liveclient.NewLiveClient(r.ClientName, r.ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r.client.EventManager().RegisterA(
|
||||
liveclient.EventMessageReceive,
|
||||
"controller.danmu.command",
|
||||
msgHandler)
|
||||
return nil
|
||||
}
|
||||
|
||||
type LiveRoomController struct {
|
||||
LiveRoomPath string
|
||||
liveRooms []*coreLiveRoom
|
||||
danmuCommands []controller.DanmuCommandExecutor
|
||||
}
|
||||
|
||||
func NewLiveRoomController() controller.ILiveRoomController {
|
||||
lr := &LiveRoomController{
|
||||
LiveRoomPath: "liverooms.json",
|
||||
liveRooms: []*coreLiveRoom{
|
||||
{LiveRoom: model.LiveRoom{
|
||||
ClientName: "bilibili",
|
||||
ID: "9076804",
|
||||
AutoConnect: false,
|
||||
}},
|
||||
{LiveRoom: model.LiveRoom{
|
||||
ClientName: "bilibili",
|
||||
ID: "3819533",
|
||||
AutoConnect: false,
|
||||
}},
|
||||
},
|
||||
danmuCommands: make([]controller.DanmuCommandExecutor, 0),
|
||||
}
|
||||
config.LoadConfig(lr)
|
||||
lr.initialize()
|
||||
return lr
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) danmuCommandHandler(event *event.Event) {
|
||||
danmu := event.Data.(*liveclient.DanmuMessage)
|
||||
args := strings.Split(danmu.Message, " ")
|
||||
if len(args[0]) == 0 {
|
||||
return
|
||||
}
|
||||
for _, cmd := range lr.danmuCommands {
|
||||
if cmd.Match(args[0]) {
|
||||
cmd.Execute(args[0], args[1:], danmu)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) initialize() {
|
||||
for i := 0; i < len(lr.liveRooms); i++ {
|
||||
if lr.liveRooms[i].client == nil {
|
||||
_ = lr.liveRooms[i].init(lr.danmuCommandHandler)
|
||||
}
|
||||
}
|
||||
go func() {
|
||||
for i := 0; i < len(lr.liveRooms); i++ {
|
||||
if lr.liveRooms[i].AutoConnect {
|
||||
lr.liveRooms[i].client.Connect()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) Name() string {
|
||||
return "LiveRooms"
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) Size() int {
|
||||
return len(lr.liveRooms)
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) OnLoad() {
|
||||
rooms := make([]model.LiveRoom, 0)
|
||||
_ = config.LoadJson(lr.LiveRoomPath, &lr.liveRooms)
|
||||
if len(rooms) == 0 {
|
||||
return
|
||||
}
|
||||
lr.liveRooms = make([]*coreLiveRoom, len(rooms))
|
||||
for i := 0; i < len(rooms); i++ {
|
||||
lr.liveRooms[i] = &coreLiveRoom{LiveRoom: rooms[i]}
|
||||
}
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) OnSave() {
|
||||
rooms := make([]model.LiveRoom, len(lr.liveRooms))
|
||||
for i := 0; i < len(lr.liveRooms); i++ {
|
||||
rooms[i] = lr.liveRooms[i].LiveRoom
|
||||
}
|
||||
_ = config.SaveJson(lr.LiveRoomPath, &rooms)
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) Get(index int) controller.ILiveRoom {
|
||||
if index < 0 || index >= len(lr.liveRooms) {
|
||||
return nil
|
||||
}
|
||||
return lr.liveRooms[index]
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) GetRoomStatus(index int) bool {
|
||||
if index < 0 || index >= len(lr.liveRooms) {
|
||||
return false
|
||||
}
|
||||
return lr.liveRooms[index].client.Status()
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) Connect(index int) error {
|
||||
lg.Infof("[LiveRooms] Try to start LiveRooms.index=%d", index)
|
||||
if index < 0 || index >= len(lr.liveRooms) {
|
||||
lg.Errorf("[LiveRooms] LiveRooms.index=%d not found", index)
|
||||
return errors.New("index out of range")
|
||||
}
|
||||
lr.liveRooms[index].client.Connect()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) Disconnect(index int) error {
|
||||
lg.Infof("[LiveRooms] Try to Disconnect LiveRooms.index=%d", index)
|
||||
if index < 0 || index >= len(lr.liveRooms) {
|
||||
lg.Errorf("[LiveRooms] LiveRooms.index=%d not found", index)
|
||||
return errors.New("index out of range")
|
||||
}
|
||||
lr.liveRooms[index].client.Disconnect()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) AddRoom(clientName, roomId string) (*model.LiveRoom, error) {
|
||||
rm := &coreLiveRoom{
|
||||
LiveRoom: model.LiveRoom{
|
||||
ClientName: clientName,
|
||||
ID: roomId,
|
||||
AutoConnect: false,
|
||||
},
|
||||
}
|
||||
lg.Infof("[LiveRooms] add live room %s", &rm.LiveRoom)
|
||||
err := rm.init(lr.danmuCommandHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lg.Infof("[LiveRooms] %s init failed: %s", &rm.LiveRoom, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lr.liveRooms = append(lr.liveRooms, rm)
|
||||
return &rm.LiveRoom, nil
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) DeleteRoom(index int) error {
|
||||
lg.Infof("Try to remove LiveRooms.index=%d", index)
|
||||
if index < 0 || index >= len(lr.liveRooms) {
|
||||
lg.Warnf("LiveRooms.index=%d not found", index)
|
||||
return errors.New("index out of range")
|
||||
}
|
||||
if len(lr.liveRooms) == 1 {
|
||||
return errors.New("can't delete last room")
|
||||
}
|
||||
_ = lr.liveRooms[index].client.Disconnect()
|
||||
lr.liveRooms[index].EventManager().UnregisterAll()
|
||||
lr.liveRooms = append(lr.liveRooms[:index], lr.liveRooms[index+1:]...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) AddDanmuCommand(executor controller.DanmuCommandExecutor) {
|
||||
lr.danmuCommands = append(lr.danmuCommands, executor)
|
||||
}
|
||||
56
controller/core/lyric.go
Normal file
56
controller/core/lyric.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/model"
|
||||
)
|
||||
|
||||
type LyricLoader struct {
|
||||
Lyric *model.Lyric
|
||||
Handler *event.Manager
|
||||
prev float64
|
||||
}
|
||||
|
||||
func NewLyricLoader() *LyricLoader {
|
||||
return &LyricLoader{
|
||||
Lyric: model.LoadLyric(""),
|
||||
Handler: event.MainManager.NewChildManager(),
|
||||
prev: -1,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LyricLoader) EventManager() *event.Manager {
|
||||
return l.Handler
|
||||
}
|
||||
|
||||
func (l *LyricLoader) Get() *model.Lyric {
|
||||
return l.Lyric
|
||||
}
|
||||
|
||||
func (l *LyricLoader) Reload(lyric string) {
|
||||
l.Lyric = model.LoadLyric(lyric)
|
||||
l.Handler.CallA(
|
||||
model.EventLyricReload,
|
||||
model.LyricReloadEvent{
|
||||
Lyrics: l.Lyric,
|
||||
})
|
||||
}
|
||||
|
||||
func (l *LyricLoader) Update(time float64) {
|
||||
lrc := l.Lyric.FindContext(time, 1, 3)
|
||||
if lrc == nil {
|
||||
return
|
||||
}
|
||||
if l.prev == lrc.Now.Time {
|
||||
return
|
||||
}
|
||||
l.prev = lrc.Now.Time
|
||||
l.Handler.CallA(
|
||||
model.EventLyricUpdate,
|
||||
model.LyricUpdateEvent{
|
||||
Lyrics: l.Lyric,
|
||||
Time: time,
|
||||
Lyric: lrc,
|
||||
})
|
||||
return
|
||||
}
|
||||
248
controller/core/playcontrol.go
Normal file
248
controller/core/playcontrol.go
Normal file
@@ -0,0 +1,248 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/controller"
|
||||
"AynaLivePlayer/model"
|
||||
"AynaLivePlayer/player"
|
||||
"AynaLivePlayer/repo/provider"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type PlayController struct {
|
||||
eventManager *event.Manager `ini:"-"`
|
||||
player player.IPlayer `ini:"-"`
|
||||
playlist controller.IPlaylistController `ini:"-"`
|
||||
provider controller.IProviderController `ini:"-"`
|
||||
lyric controller.ILyricLoader `ini:"-"`
|
||||
playing *model.Media `ini:"-"`
|
||||
AudioDevice string
|
||||
Volume float64
|
||||
SkipPlaylist bool
|
||||
}
|
||||
|
||||
func (pc *PlayController) GetSkipPlaylist() bool {
|
||||
return pc.SkipPlaylist
|
||||
}
|
||||
|
||||
func (pc *PlayController) SetSkipPlaylist(b bool) {
|
||||
pc.SkipPlaylist = b
|
||||
}
|
||||
|
||||
func (pc *PlayController) Name() string {
|
||||
return "PlayController"
|
||||
}
|
||||
|
||||
func (pc *PlayController) OnLoad() {
|
||||
return
|
||||
}
|
||||
|
||||
func (pc *PlayController) OnSave() {
|
||||
return
|
||||
}
|
||||
|
||||
func NewPlayerController(
|
||||
player player.IPlayer,
|
||||
playlist controller.IPlaylistController,
|
||||
lyric controller.ILyricLoader,
|
||||
provider controller.IProviderController) controller.IPlayController {
|
||||
pc := &PlayController{
|
||||
eventManager: event.MainManager.NewChildManager(),
|
||||
player: player,
|
||||
playlist: playlist,
|
||||
lyric: lyric,
|
||||
provider: provider,
|
||||
playing: &model.Media{},
|
||||
AudioDevice: "auto",
|
||||
Volume: 100,
|
||||
SkipPlaylist: false,
|
||||
}
|
||||
config.LoadConfig(pc)
|
||||
pc.SetVolume(pc.Volume)
|
||||
pc.SetAudioDevice(pc.AudioDevice)
|
||||
pc.player.ObserveProperty(model.PlayerPropIdleActive, "controller.playcontrol.idleplaynext", pc.handleMpvIdlePlayNext)
|
||||
pc.playlist.GetCurrent().EventManager().RegisterA(model.EventPlaylistInsert, "controller.playcontrol.playlistadd", pc.handlePlaylistAdd)
|
||||
pc.player.ObserveProperty(model.PlayerPropTimePos, "controller.playcontrol.updatelyric", pc.handleLyricUpdate)
|
||||
return pc
|
||||
}
|
||||
|
||||
func (pc *PlayController) handleMpvIdlePlayNext(event *event.Event) {
|
||||
isIdle := event.Data.(model.PlayerPropertyUpdateEvent).Value.(bool)
|
||||
if isIdle {
|
||||
lg.Info("[Controller] mpv went idle, try play next")
|
||||
pc.PlayNext()
|
||||
}
|
||||
}
|
||||
|
||||
func (pc *PlayController) handlePlaylistAdd(event *event.Event) {
|
||||
if pc.player.IsIdle() {
|
||||
pc.PlayNext()
|
||||
return
|
||||
}
|
||||
lg.Debugf("[PlayController] playlist add event, SkipPlaylist=%t", pc.SkipPlaylist)
|
||||
if pc.SkipPlaylist && pc.playing != nil && pc.playing.User == controller.PlaylistUser {
|
||||
pc.PlayNext()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (pc *PlayController) handleLyricUpdate(event *event.Event) {
|
||||
data := event.Data.(model.PlayerPropertyUpdateEvent).Value
|
||||
if data == nil {
|
||||
return
|
||||
}
|
||||
pc.lyric.Update(data.(float64))
|
||||
}
|
||||
|
||||
func (pc *PlayController) EventManager() *event.Manager {
|
||||
return pc.eventManager
|
||||
}
|
||||
|
||||
func (pc *PlayController) GetPlaying() *model.Media {
|
||||
return pc.playing
|
||||
}
|
||||
|
||||
func (pc *PlayController) GetPlayer() player.IPlayer {
|
||||
return pc.player
|
||||
}
|
||||
|
||||
func (pc *PlayController) GetLyric() controller.ILyricLoader {
|
||||
return pc.lyric
|
||||
}
|
||||
|
||||
func (pc *PlayController) PlayNext() {
|
||||
lg.Infof("[PlayController] try to play next possible media")
|
||||
if pc.playlist.GetCurrent().Size() == 0 && pc.playlist.GetDefault().Size() == 0 {
|
||||
return
|
||||
}
|
||||
var media *model.Media
|
||||
if pc.playlist.GetCurrent().Size() != 0 {
|
||||
media = pc.playlist.GetCurrent().Pop().Copy()
|
||||
} else if pc.playlist.GetDefault().Size() != 0 {
|
||||
media = pc.playlist.GetDefault().Next().Copy()
|
||||
media.User = controller.PlaylistUser
|
||||
}
|
||||
_ = pc.Play(media)
|
||||
}
|
||||
|
||||
func (pc *PlayController) Play(media *model.Media) error {
|
||||
lg.Infof("[PlayController] prepare media %s", media.Title)
|
||||
err := pc.provider.PrepareMedia(media)
|
||||
if err != nil {
|
||||
lg.Warn("[PlayController] prepare media failed, try play next")
|
||||
//pc.PlayNext()
|
||||
return errors.New("prepare media failed")
|
||||
}
|
||||
pc.eventManager.CallA(model.EventPlay, model.PlayEvent{
|
||||
Media: media,
|
||||
})
|
||||
pc.playing = media
|
||||
pc.playlist.AddToHistory(media)
|
||||
if err := pc.player.Play(media); err != nil {
|
||||
lg.Warn("[PlayController] play failed", err)
|
||||
return errors.New("player play failed")
|
||||
}
|
||||
pc.eventManager.CallA(model.EventPlayed, model.PlayEvent{
|
||||
Media: media,
|
||||
})
|
||||
pc.lyric.Reload(media.Lyric)
|
||||
// reset
|
||||
media.Url = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pc *PlayController) Add(keyword string, user interface{}) {
|
||||
media := pc.provider.MediaMatch(keyword)
|
||||
if media == nil {
|
||||
medias, err := pc.provider.Search(keyword)
|
||||
if err != nil {
|
||||
lg.Warnf("[PlayController] search for %s, got error %s", keyword, err)
|
||||
return
|
||||
}
|
||||
if len(medias) == 0 {
|
||||
lg.Info("[PlayController] search for %s, got no result", keyword)
|
||||
return
|
||||
}
|
||||
media = medias[0]
|
||||
}
|
||||
media.User = user
|
||||
lg.Infof("[PlayController] add media %s (%s)", media.Title, media.Artist)
|
||||
pc.playlist.GetCurrent().Insert(-1, media)
|
||||
}
|
||||
|
||||
func (pc *PlayController) AddWithProvider(keyword string, pname string, user interface{}) {
|
||||
media := provider.MatchMedia(pname, keyword)
|
||||
if media == nil {
|
||||
medias, err := provider.Search(pname, keyword)
|
||||
if err != nil {
|
||||
lg.Warnf("[PlayController] search for %s, got error %s", keyword, err)
|
||||
return
|
||||
}
|
||||
if len(medias) == 0 {
|
||||
lg.Infof("[PlayController] search for %s, got no result", keyword)
|
||||
return
|
||||
}
|
||||
|
||||
media = medias[0]
|
||||
}
|
||||
media.User = user
|
||||
lg.Infof("[PlayController] add media %s (%s)", media.Title, media.Artist)
|
||||
pc.playlist.GetCurrent().Insert(-1, media)
|
||||
}
|
||||
|
||||
func (pc *PlayController) Seek(position float64, absolute bool) {
|
||||
if err := pc.player.Seek(position, absolute); err != nil {
|
||||
lg.Warnf("[PlayController] seek to position %f (%t) failed, %s", position, absolute, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (pc *PlayController) Toggle() (b bool) {
|
||||
var err error
|
||||
if pc.player.IsPaused() {
|
||||
err = pc.player.Unpause()
|
||||
b = false
|
||||
} else {
|
||||
err = pc.player.Pause()
|
||||
b = true
|
||||
}
|
||||
if err != nil {
|
||||
lg.Warn("[PlayController] toggle failed", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (pc *PlayController) SetVolume(volume float64) {
|
||||
if pc.player.SetVolume(volume) != nil {
|
||||
lg.Warnf("[PlayController] set mpv volume to %f failed", volume)
|
||||
return
|
||||
}
|
||||
pc.Volume = volume
|
||||
}
|
||||
|
||||
func (pc *PlayController) Destroy() {
|
||||
pc.player.Stop()
|
||||
}
|
||||
|
||||
func (pc *PlayController) GetCurrentAudioDevice() string {
|
||||
return pc.AudioDevice
|
||||
}
|
||||
|
||||
func (pc *PlayController) GetAudioDevices() []model.AudioDevice {
|
||||
dl, err := pc.player.GetAudioDeviceList()
|
||||
if err != nil {
|
||||
return make([]model.AudioDevice, 0)
|
||||
}
|
||||
return dl
|
||||
}
|
||||
|
||||
func (pc *PlayController) SetAudioDevice(device string) {
|
||||
lg.Infof("[PlayController] set audio device to %s", device)
|
||||
if err := pc.player.SetAudioDevice(device); err != nil {
|
||||
lg.Warnf("[PlayController] set mpv audio device to %s failed, %s", device, err)
|
||||
_ = pc.player.SetAudioDevice("auto")
|
||||
pc.AudioDevice = "auto"
|
||||
return
|
||||
}
|
||||
pc.AudioDevice = device
|
||||
}
|
||||
397
controller/core/playlist.go
Normal file
397
controller/core/playlist.go
Normal file
@@ -0,0 +1,397 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/controller"
|
||||
"AynaLivePlayer/model"
|
||||
"AynaLivePlayer/repo/provider"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type PlaylistController struct {
|
||||
PlaylistPath string
|
||||
provider controller.IProviderController
|
||||
History controller.IPlaylist `ini:"-"`
|
||||
Current controller.IPlaylist `ini:"-"`
|
||||
Default controller.IPlaylist `ini:"-"`
|
||||
Playlists []controller.IPlaylist `ini:"-"`
|
||||
DefaultIndex int
|
||||
CurrentPlaylistRandom bool
|
||||
DefaultPlaylistRandom bool
|
||||
}
|
||||
|
||||
func NewPlaylistController(
|
||||
provider controller.IProviderController) controller.IPlaylistController {
|
||||
pc := &PlaylistController{
|
||||
PlaylistPath: "playlists.json",
|
||||
provider: provider,
|
||||
History: NewPlaylist("history"),
|
||||
Default: NewPlaylist("default"),
|
||||
Current: NewPlaylist("current"),
|
||||
Playlists: make([]controller.IPlaylist, 0),
|
||||
DefaultIndex: 0,
|
||||
CurrentPlaylistRandom: false,
|
||||
DefaultPlaylistRandom: true,
|
||||
}
|
||||
config.LoadConfig(pc)
|
||||
if pc.DefaultIndex < 0 || pc.DefaultIndex >= len(pc.Playlists) {
|
||||
pc.DefaultIndex = 0
|
||||
lg.Warn("playlist index did not find")
|
||||
}
|
||||
go func() {
|
||||
_ = pc.SetDefault(pc.DefaultIndex)
|
||||
}()
|
||||
return pc
|
||||
}
|
||||
|
||||
func (pc *PlaylistController) Name() string {
|
||||
return "Playlists"
|
||||
}
|
||||
|
||||
func (pc *PlaylistController) OnLoad() {
|
||||
var metas = []model.Meta{
|
||||
{
|
||||
"netease",
|
||||
"2382819181",
|
||||
},
|
||||
{"netease",
|
||||
"4987059624",
|
||||
},
|
||||
{"local",
|
||||
"list1",
|
||||
},
|
||||
}
|
||||
_ = config.LoadJson(pc.PlaylistPath, &metas)
|
||||
for _, m := range metas {
|
||||
p := NewPlaylist(fmt.Sprintf("%s-%s", m.Name, m.Id))
|
||||
p.Model().Meta = m
|
||||
pc.Playlists = append(pc.Playlists, p)
|
||||
}
|
||||
if pc.CurrentPlaylistRandom {
|
||||
pc.Current.Model().Mode = model.PlaylistModeRandom
|
||||
}
|
||||
if pc.DefaultPlaylistRandom {
|
||||
pc.Default.Model().Mode = model.PlaylistModeRandom
|
||||
}
|
||||
}
|
||||
|
||||
func (pc *PlaylistController) OnSave() {
|
||||
var metas = make([]model.Meta, 0)
|
||||
for _, pl := range pc.Playlists {
|
||||
metas = append(metas, pl.Model().Meta)
|
||||
}
|
||||
_ = config.SaveJson(pc.PlaylistPath, &metas)
|
||||
|
||||
if pc.Current.Model().Mode == model.PlaylistModeRandom {
|
||||
pc.CurrentPlaylistRandom = true
|
||||
} else {
|
||||
pc.CurrentPlaylistRandom = false
|
||||
}
|
||||
if pc.Default.Model().Mode == model.PlaylistModeRandom {
|
||||
pc.DefaultPlaylistRandom = true
|
||||
} else {
|
||||
pc.DefaultPlaylistRandom = false
|
||||
}
|
||||
}
|
||||
|
||||
func (pc *PlaylistController) Size() int {
|
||||
return len(pc.Playlists)
|
||||
}
|
||||
|
||||
func (pc *PlaylistController) GetHistory() controller.IPlaylist {
|
||||
return pc.History
|
||||
}
|
||||
|
||||
func (pc *PlaylistController) GetDefault() controller.IPlaylist {
|
||||
return pc.Default
|
||||
}
|
||||
|
||||
func (pc *PlaylistController) GetCurrent() controller.IPlaylist {
|
||||
return pc.Current
|
||||
}
|
||||
|
||||
func (pc *PlaylistController) AddToHistory(media *model.Media) {
|
||||
lg.Tracef("add media %s (%s) to history", media.Title, media.Artist)
|
||||
media = media.Copy()
|
||||
// reset url for future use
|
||||
media.Url = ""
|
||||
if pc.History.Size() >= 1024 {
|
||||
pc.History.Replace([]*model.Media{})
|
||||
}
|
||||
media.User = controller.HistoryUser
|
||||
pc.History.Push(media)
|
||||
return
|
||||
}
|
||||
|
||||
func (pc *PlaylistController) Get(index int) controller.IPlaylist {
|
||||
if index < 0 || index >= len(pc.Playlists) {
|
||||
lg.Warnf("playlist.index=%d not found", index)
|
||||
return nil
|
||||
}
|
||||
return pc.Playlists[index]
|
||||
}
|
||||
|
||||
func (pc *PlaylistController) Add(pname string, uri string) controller.IPlaylist {
|
||||
lg.Infof("try add playlist %s with provider %s", uri, pname)
|
||||
id, err := provider.FormatPlaylistUrl(pname, uri)
|
||||
if err != nil || id == "" {
|
||||
lg.Warnf("fail to format %s playlist id for %s", uri, pname)
|
||||
return nil
|
||||
}
|
||||
p := NewPlaylist(fmt.Sprintf("%s-%s", pname, id))
|
||||
p.Model().Meta = model.Meta{
|
||||
Name: pname,
|
||||
Id: id,
|
||||
}
|
||||
pc.Playlists = append(pc.Playlists, p)
|
||||
return p
|
||||
}
|
||||
|
||||
func (pc *PlaylistController) Remove(index int) controller.IPlaylist {
|
||||
lg.Infof("Try to remove playlist.index=%d", index)
|
||||
if index < 0 || index >= len(pc.Playlists) {
|
||||
lg.Warnf("playlist.index=%d not found", index)
|
||||
return nil
|
||||
}
|
||||
if index == pc.DefaultIndex {
|
||||
lg.Info("Delete current system playlist, reset system playlist to index = 0")
|
||||
_ = pc.SetDefault(0)
|
||||
}
|
||||
if index < pc.DefaultIndex {
|
||||
lg.Debugf("Delete playlist before system playlist (index=%d), reduce system playlist index by 1", pc.DefaultIndex)
|
||||
pc.DefaultIndex = pc.DefaultIndex - 1
|
||||
}
|
||||
pl := pc.Playlists[index]
|
||||
pc.Playlists = append(pc.Playlists[:index], pc.Playlists[index+1:]...)
|
||||
return pl
|
||||
}
|
||||
|
||||
func (pc *PlaylistController) SetDefault(index int) error {
|
||||
lg.Infof("try set system playlist to playlist.id=%d", index)
|
||||
if index < 0 || index >= len(pc.Playlists) {
|
||||
lg.Warn("playlist.index=%d not found", index)
|
||||
return errors.New("playlist.index not found")
|
||||
}
|
||||
err := pc.provider.PreparePlaylist(pc.Playlists[index])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pl := pc.Playlists[index].Model().Copy()
|
||||
pc.DefaultIndex = index
|
||||
controller.ApplyUser(pl.Medias, controller.PlaylistUser)
|
||||
pc.Default.Replace(pl.Medias)
|
||||
pc.Default.Model().Name = pl.Name
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pc *PlaylistController) PreparePlaylistByIndex(index int) error {
|
||||
lg.Infof("try prepare playlist.id=%d", index)
|
||||
if index < 0 || index >= len(pc.Playlists) {
|
||||
lg.Warn("playlist.id=%d not found", index)
|
||||
return nil
|
||||
}
|
||||
return pc.provider.PreparePlaylist(pc.Playlists[index])
|
||||
}
|
||||
|
||||
type corePlaylist struct {
|
||||
model.Playlist
|
||||
Index int
|
||||
Lock sync.RWMutex
|
||||
eventManager *event.Manager
|
||||
}
|
||||
|
||||
func NewPlaylist(name string) controller.IPlaylist {
|
||||
return &corePlaylist{
|
||||
Index: 0,
|
||||
Playlist: model.Playlist{
|
||||
Name: name,
|
||||
Medias: make([]*model.Media, 0),
|
||||
Mode: model.PlaylistModeNormal,
|
||||
Meta: model.Meta{},
|
||||
},
|
||||
eventManager: event.MainManager.NewChildManager(),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *corePlaylist) Model() *model.Playlist {
|
||||
return &p.Playlist
|
||||
}
|
||||
|
||||
func (p *corePlaylist) EventManager() *event.Manager {
|
||||
return p.eventManager
|
||||
}
|
||||
|
||||
func (p *corePlaylist) Name() string {
|
||||
return p.Playlist.Name
|
||||
}
|
||||
|
||||
func (p *corePlaylist) Size() int {
|
||||
return p.Playlist.Size()
|
||||
}
|
||||
|
||||
func (p *corePlaylist) Get(index int) *model.Media {
|
||||
if index < 0 || index >= p.Playlist.Size() {
|
||||
return nil
|
||||
}
|
||||
return p.Playlist.Medias[index]
|
||||
}
|
||||
|
||||
func (p *corePlaylist) Pop() *model.Media {
|
||||
lg.Info("[Playlists] %s pop first media", p.Playlist)
|
||||
if p.Size() == 0 {
|
||||
return nil
|
||||
}
|
||||
p.Lock.Lock()
|
||||
index := 0
|
||||
if p.Mode == model.PlaylistModeRandom {
|
||||
index = rand.Intn(p.Size())
|
||||
}
|
||||
m := p.Medias[index]
|
||||
for i := index; i > 0; i-- {
|
||||
p.Medias[i] = p.Medias[i-1]
|
||||
}
|
||||
p.Medias = p.Medias[1:]
|
||||
p.Lock.Unlock()
|
||||
if m == nil {
|
||||
lg.Warn("[Playlists] pop first media failed, no media left in the playlist")
|
||||
return nil
|
||||
}
|
||||
p.eventManager.CallA(
|
||||
model.EventPlaylistUpdate,
|
||||
model.PlaylistUpdateEvent{Playlist: p.Playlist.Copy()},
|
||||
)
|
||||
return m
|
||||
}
|
||||
|
||||
func (p *corePlaylist) Replace(medias []*model.Media) {
|
||||
lg.Infof("[Playlists] %s replace all media", &p.Playlist)
|
||||
p.Lock.Lock()
|
||||
p.Playlist.Medias = medias
|
||||
p.Index = 0
|
||||
p.Lock.Unlock()
|
||||
p.eventManager.CallA(
|
||||
model.EventPlaylistUpdate,
|
||||
model.PlaylistUpdateEvent{Playlist: p.Playlist.Copy()},
|
||||
)
|
||||
}
|
||||
|
||||
func (p *corePlaylist) Push(media *model.Media) {
|
||||
p.Insert(-1, media)
|
||||
}
|
||||
|
||||
func (p *corePlaylist) Insert(index int, media *model.Media) {
|
||||
lg.Infof("[Playlists]insert media into new index %d at %s ", index, p.Playlist)
|
||||
lg.Debugf("media=%s %v", media.Title, media.Meta)
|
||||
e := event.Event{
|
||||
Id: model.EventPlaylistPreInsert,
|
||||
Cancelled: false,
|
||||
Data: model.PlaylistInsertEvent{
|
||||
Playlist: p.Playlist.Copy(),
|
||||
Index: index,
|
||||
Media: media,
|
||||
},
|
||||
}
|
||||
p.eventManager.Call(&e)
|
||||
if e.Cancelled {
|
||||
lg.Info("[Playlists] media insertion has been cancelled by handler")
|
||||
return
|
||||
}
|
||||
p.Lock.Lock()
|
||||
if index > p.Size() {
|
||||
index = p.Size()
|
||||
}
|
||||
if index < 0 {
|
||||
index = p.Size() + index + 1
|
||||
}
|
||||
p.Medias = append(p.Medias, nil)
|
||||
for i := p.Size() - 1; i > index; i-- {
|
||||
p.Medias[i] = p.Medias[i-1]
|
||||
}
|
||||
p.Medias[index] = media
|
||||
p.Lock.Unlock()
|
||||
p.eventManager.CallA(
|
||||
model.EventPlaylistUpdate,
|
||||
model.PlaylistUpdateEvent{Playlist: p.Playlist.Copy()},
|
||||
)
|
||||
p.eventManager.CallA(
|
||||
model.EventPlaylistInsert,
|
||||
model.PlaylistInsertEvent{
|
||||
Playlist: p.Playlist.Copy(),
|
||||
Index: index,
|
||||
Media: media,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (p *corePlaylist) Delete(index int) *model.Media {
|
||||
lg.Infof("from media at index %d from %s", index, p.Playlist)
|
||||
if index >= p.Size() || index < 0 {
|
||||
p.Lock.Unlock()
|
||||
return nil
|
||||
}
|
||||
m := p.Medias[index]
|
||||
p.Lock.Lock()
|
||||
// todo: @5 delete optimization
|
||||
p.Medias = append(p.Medias[:index], p.Medias[index+1:]...)
|
||||
p.Lock.Unlock()
|
||||
if m == nil {
|
||||
lg.Warnf("media at index %d does not exist", index)
|
||||
}
|
||||
p.eventManager.CallA(
|
||||
model.EventPlaylistUpdate,
|
||||
model.PlaylistUpdateEvent{Playlist: p.Playlist.Copy()})
|
||||
return m
|
||||
}
|
||||
|
||||
func (p *corePlaylist) Move(src int, dst int) {
|
||||
lg.Infof("from media from index %d to %d", src, dst)
|
||||
if src >= p.Size() || src < 0 {
|
||||
lg.Warnf("media at index %d does not exist", src)
|
||||
return
|
||||
}
|
||||
p.Lock.Lock()
|
||||
if dst >= p.Size() {
|
||||
dst = p.Size() - 1
|
||||
}
|
||||
if dst < 0 {
|
||||
dst = 0
|
||||
}
|
||||
if dst == src {
|
||||
p.Lock.Unlock()
|
||||
return
|
||||
}
|
||||
step := 1
|
||||
if dst < src {
|
||||
step = -1
|
||||
}
|
||||
tmp := p.Medias[src]
|
||||
for i := src; i != dst; i += step {
|
||||
p.Medias[i] = p.Medias[i+step]
|
||||
}
|
||||
p.Medias[dst] = tmp
|
||||
p.Lock.Unlock()
|
||||
p.eventManager.CallA(
|
||||
model.EventPlaylistUpdate,
|
||||
model.PlaylistUpdateEvent{Playlist: p.Playlist.Copy()})
|
||||
}
|
||||
|
||||
func (p *corePlaylist) Next() *model.Media {
|
||||
lg.Infof("[Playlists] %s get next media with random=%t", p, p.Mode == model.PlaylistModeRandom)
|
||||
if p.Size() == 0 {
|
||||
lg.Warn("[Playlists] get next media failed, no media left in the playlist")
|
||||
return nil
|
||||
}
|
||||
var index int
|
||||
index = p.Index
|
||||
if p.Mode == model.PlaylistModeRandom {
|
||||
p.Index = rand.Intn(p.Size())
|
||||
} else {
|
||||
p.Index = (p.Index + 1) % p.Size()
|
||||
}
|
||||
m := p.Medias[index]
|
||||
return m
|
||||
}
|
||||
44
controller/core/plugin.go
Normal file
44
controller/core/plugin.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/controller"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type PluginController struct {
|
||||
plugins map[string]controller.Plugin
|
||||
}
|
||||
|
||||
func NewPluginController() controller.IPluginController {
|
||||
return &PluginController{
|
||||
plugins: make(map[string]controller.Plugin),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PluginController) LoadPlugin(plugin controller.Plugin) {
|
||||
lg.Info("[Plugin] Loading plugin: " + plugin.Name())
|
||||
if _, ok := p.plugins[plugin.Name()]; ok {
|
||||
logrus.Warnf("[Plugin] plugin with same name already exists, skip")
|
||||
return
|
||||
}
|
||||
if err := plugin.Enable(); err != nil {
|
||||
lg.Warnf("Failed to load plugin: %s, %s", plugin.Name(), err)
|
||||
return
|
||||
}
|
||||
p.plugins[plugin.Name()] = plugin
|
||||
}
|
||||
|
||||
func (p *PluginController) LoadPlugins(plugins ...controller.Plugin) {
|
||||
for _, plugin := range plugins {
|
||||
p.LoadPlugin(plugin)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PluginController) ClosePlugins() {
|
||||
for _, plugin := range p.plugins {
|
||||
if err := plugin.Disable(); err != nil {
|
||||
lg.Warnf("Failed to close plugin: %s, %s", plugin.Name(), err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
114
controller/core/provider.go
Normal file
114
controller/core/provider.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/controller"
|
||||
"AynaLivePlayer/model"
|
||||
"AynaLivePlayer/repo/provider"
|
||||
)
|
||||
|
||||
type ProviderController struct {
|
||||
config.BaseConfig
|
||||
Priority []string
|
||||
LocalDir string
|
||||
}
|
||||
|
||||
func (pc *ProviderController) Name() string {
|
||||
return "Provider"
|
||||
}
|
||||
|
||||
func NewProviderController() controller.IProviderController {
|
||||
p := &ProviderController{
|
||||
Priority: []string{"netease", "kuwo", "bilibili", "local", "bilibili-video"},
|
||||
LocalDir: "./music",
|
||||
}
|
||||
config.LoadConfig(p)
|
||||
provider.NewLocal(p.LocalDir)
|
||||
return p
|
||||
}
|
||||
|
||||
func (pc *ProviderController) GetPriority() []string {
|
||||
return pc.Priority
|
||||
}
|
||||
|
||||
func (pc *ProviderController) PrepareMedia(media *model.Media) error {
|
||||
var err error
|
||||
if media.Title == "" || !media.Cover.Exists() {
|
||||
lg.Trace("fetching media info")
|
||||
if err = provider.UpdateMedia(media); err != nil {
|
||||
lg.Warn("fail to prepare media when fetch info", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if media.Url == "" {
|
||||
lg.Trace("fetching media url")
|
||||
if err = provider.UpdateMediaUrl(media); err != nil {
|
||||
lg.Warn("fail to prepare media when url", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if media.Lyric == "" {
|
||||
lg.Trace("fetching media lyric")
|
||||
if err = provider.UpdateMediaLyric(media); err != nil {
|
||||
lg.Warn("fail to prepare media when lyric", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pc *ProviderController) MediaMatch(keyword string) *model.Media {
|
||||
lg.Infof("Match media for %s", keyword)
|
||||
for _, p := range pc.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 {
|
||||
lg.Warnf("Provider %s not exist", p)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pc *ProviderController) Search(keyword string) ([]*model.Media, error) {
|
||||
lg.Infof("Search for %s", keyword)
|
||||
for _, p := range pc.Priority {
|
||||
if pr, ok := provider.Providers[p]; ok {
|
||||
r, err := pr.Search(keyword)
|
||||
if err != nil {
|
||||
lg.Warn("Provider %s return err", err)
|
||||
continue
|
||||
}
|
||||
return r, err
|
||||
} else {
|
||||
lg.Warnf("Provider %s not exist", p)
|
||||
}
|
||||
}
|
||||
return nil, provider.ErrorNoSuchProvider
|
||||
}
|
||||
|
||||
func (pc *ProviderController) SearchWithProvider(keyword string, p string) ([]*model.Media, error) {
|
||||
lg.Infof("Search for %s using %s", keyword, p)
|
||||
if pr, ok := provider.Providers[p]; ok {
|
||||
r, err := pr.Search(keyword)
|
||||
return r, err
|
||||
}
|
||||
lg.Warnf("Provider %s not exist", p)
|
||||
return nil, provider.ErrorNoSuchProvider
|
||||
}
|
||||
|
||||
func (pc *ProviderController) PreparePlaylist(playlist controller.IPlaylist) error {
|
||||
lg.Debug("Prepare playlist ", playlist.Name())
|
||||
medias, err := provider.GetPlaylist(&playlist.Model().Meta)
|
||||
if err != nil {
|
||||
lg.Warn("prepare playlist failed ", err)
|
||||
return err
|
||||
}
|
||||
controller.ApplyUser(medias, controller.SystemUser)
|
||||
playlist.Replace(medias)
|
||||
return nil
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/event"
|
||||
"AynaLivePlayer/liveclient"
|
||||
)
|
||||
|
||||
var DanmuHandlers []DanmuHandler
|
||||
|
||||
type DanmuHandler interface {
|
||||
Execute(anmu *liveclient.DanmuMessage)
|
||||
}
|
||||
|
||||
func AddDanmuHandler(handlers ...DanmuHandler) {
|
||||
DanmuHandlers = append(DanmuHandlers, handlers...)
|
||||
}
|
||||
|
||||
func danmuHandler(event *event.Event) {
|
||||
danmu := event.Data.(*liveclient.DanmuMessage)
|
||||
for _, cmd := range DanmuHandlers {
|
||||
cmd.Execute(danmu)
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/liveclient"
|
||||
"AynaLivePlayer/player"
|
||||
"AynaLivePlayer/provider"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var MainPlayer *player.Player
|
||||
var UserPlaylist *player.Playlist
|
||||
var History *player.Playlist
|
||||
var HistoryUser *player.User
|
||||
var SystemPlaylist *player.Playlist
|
||||
var LiveClient liveclient.LiveClient
|
||||
var PlaylistManager []*player.Playlist
|
||||
var CurrentLyric *player.Lyric
|
||||
var CurrentMedia *player.Media
|
||||
|
||||
func Initialize() {
|
||||
|
||||
MainPlayer = player.NewPlayer()
|
||||
SetAudioDevice(config.Player.AudioDevice)
|
||||
SetVolume(config.Player.Volume)
|
||||
UserPlaylist = player.NewPlaylist("user", player.PlaylistConfig{RandomNext: false})
|
||||
SystemPlaylist = player.NewPlaylist("system", player.PlaylistConfig{RandomNext: config.Player.PlaylistRandom})
|
||||
PlaylistManager = make([]*player.Playlist, 0)
|
||||
CurrentLyric = player.NewLyric("")
|
||||
loadPlaylists()
|
||||
|
||||
History = player.NewPlaylist("history", player.PlaylistConfig{RandomNext: false})
|
||||
HistoryUser = &player.User{Name: "History"}
|
||||
|
||||
MainPlayer.ObserveProperty("idle-active", handleMpvIdlePlayNext)
|
||||
UserPlaylist.Handler.RegisterA(player.EventPlaylistInsert, "controller.playnextwhenadd", handlePlaylistAdd)
|
||||
MainPlayer.ObserveProperty("time-pos", handleLyricUpdate)
|
||||
MainPlayer.Start()
|
||||
|
||||
}
|
||||
|
||||
func loadPlaylists() {
|
||||
l().Info("Loading playlists ", config.Player.Playlists, config.Player.PlaylistsProvider)
|
||||
if len(config.Player.Playlists) != len(config.Player.Playlists) {
|
||||
l().Warn("playlist id and provider does not have same length")
|
||||
return
|
||||
}
|
||||
for i := 0; i < len(config.Player.Playlists); i++ {
|
||||
pname := config.Player.PlaylistsProvider[i]
|
||||
id := config.Player.Playlists[i]
|
||||
p := player.NewPlaylist(fmt.Sprintf("%s-%s", pname, id), player.PlaylistConfig{})
|
||||
p.Meta = provider.Meta{
|
||||
Name: pname,
|
||||
Id: id,
|
||||
}
|
||||
PlaylistManager = append(PlaylistManager, p)
|
||||
}
|
||||
if config.Player.PlaylistIndex < 0 || config.Player.PlaylistIndex >= len(config.Player.Playlists) {
|
||||
l().Warn("playlist index did not find")
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
c := config.Player.PlaylistIndex
|
||||
err := PreparePlaylist(PlaylistManager[c])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
SetSystemPlaylist(c)
|
||||
}()
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/event"
|
||||
"AynaLivePlayer/player"
|
||||
"github.com/aynakeya/go-mpv"
|
||||
)
|
||||
|
||||
func handleMpvIdlePlayNext(property *mpv.EventProperty) {
|
||||
isIdle := property.Data.(mpv.Node).Value.(bool)
|
||||
if isIdle {
|
||||
l().Info("mpv went idle, try play next")
|
||||
PlayNext()
|
||||
}
|
||||
}
|
||||
|
||||
func handlePlaylistAdd(event *event.Event) {
|
||||
if MainPlayer.IsIdle() {
|
||||
PlayNext()
|
||||
return
|
||||
}
|
||||
if config.Player.SkipPlaylist && CurrentMedia != nil && CurrentMedia.User == player.PlaylistUser {
|
||||
PlayNext()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func handleLyricUpdate(property *mpv.EventProperty) {
|
||||
if property.Data == nil {
|
||||
return
|
||||
}
|
||||
t := property.Data.(mpv.Node).Value.(float64)
|
||||
CurrentLyric.Update(t)
|
||||
}
|
||||
30
controller/liveroom.go
Normal file
30
controller/liveroom.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/liveclient"
|
||||
"AynaLivePlayer/model"
|
||||
)
|
||||
|
||||
type DanmuCommandExecutor interface {
|
||||
Match(command string) bool
|
||||
Execute(command string, args []string, danmu *liveclient.DanmuMessage)
|
||||
}
|
||||
|
||||
type ILiveRoomController interface {
|
||||
Size() int
|
||||
Get(index int) ILiveRoom
|
||||
GetRoomStatus(index int) bool
|
||||
Connect(index int) error
|
||||
Disconnect(index int) error
|
||||
AddRoom(clientName, roomId string) (*model.LiveRoom, error)
|
||||
DeleteRoom(index int) error
|
||||
AddDanmuCommand(executor DanmuCommandExecutor)
|
||||
}
|
||||
|
||||
type ILiveRoom interface {
|
||||
Model() *model.LiveRoom // should return mutable model (not a copy)
|
||||
Title() string // should be same as Model().Title
|
||||
Status() bool
|
||||
EventManager() *event.Manager
|
||||
}
|
||||
34
controller/playcontrol.go
Normal file
34
controller/playcontrol.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/model"
|
||||
"AynaLivePlayer/player"
|
||||
)
|
||||
|
||||
type IPlayController interface {
|
||||
EventManager() *event.Manager
|
||||
GetPlaying() *model.Media
|
||||
GetPlayer() player.IPlayer
|
||||
PlayNext()
|
||||
Play(media *model.Media) error
|
||||
Add(keyword string, user interface{})
|
||||
AddWithProvider(keyword string, provider string, user interface{})
|
||||
Seek(position float64, absolute bool)
|
||||
Toggle() bool
|
||||
SetVolume(volume float64)
|
||||
Destroy()
|
||||
GetCurrentAudioDevice() string
|
||||
GetAudioDevices() []model.AudioDevice
|
||||
SetAudioDevice(device string)
|
||||
GetLyric() ILyricLoader
|
||||
GetSkipPlaylist() bool
|
||||
SetSkipPlaylist(b bool)
|
||||
}
|
||||
|
||||
type ILyricLoader interface {
|
||||
EventManager() *event.Manager
|
||||
Get() *model.Lyric
|
||||
Reload(lyric string)
|
||||
Update(time float64)
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/player"
|
||||
"AynaLivePlayer/provider"
|
||||
)
|
||||
|
||||
func PlayNext() {
|
||||
l().Info("try to play next possible media")
|
||||
if UserPlaylist.Size() == 0 && SystemPlaylist.Size() == 0 {
|
||||
return
|
||||
}
|
||||
var media *player.Media
|
||||
if UserPlaylist.Size() != 0 {
|
||||
media = UserPlaylist.Pop()
|
||||
} else if SystemPlaylist.Size() != 0 {
|
||||
media = SystemPlaylist.Next()
|
||||
}
|
||||
Play(media)
|
||||
}
|
||||
|
||||
func Play(media *player.Media) {
|
||||
l().Info("prepare media")
|
||||
err := PrepareMedia(media)
|
||||
if err != nil {
|
||||
l().Warn("prepare media failed. try play next")
|
||||
PlayNext()
|
||||
return
|
||||
}
|
||||
CurrentMedia = media
|
||||
AddToHistory(media)
|
||||
if err := MainPlayer.Play(media); err != nil {
|
||||
l().Warn("play failed", err)
|
||||
}
|
||||
CurrentLyric.Reload(media.Lyric)
|
||||
// reset
|
||||
media.Url = ""
|
||||
}
|
||||
|
||||
func Add(keyword string, user interface{}) {
|
||||
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]
|
||||
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
|
||||
}
|
||||
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)
|
||||
UserPlaylist.Insert(-1, media)
|
||||
}
|
||||
|
||||
func Seek(position float64, absolute bool) {
|
||||
if err := MainPlayer.Seek(position, absolute); err != nil {
|
||||
l().Warnf("seek to position %f (%t) failed, %s", position, absolute, err)
|
||||
}
|
||||
}
|
||||
|
||||
func Toggle() (b bool) {
|
||||
var err error
|
||||
if MainPlayer.IsPaused() {
|
||||
err = MainPlayer.Unpause()
|
||||
b = false
|
||||
} else {
|
||||
err = MainPlayer.Pause()
|
||||
b = true
|
||||
}
|
||||
if err != nil {
|
||||
l().Warn("toggle failed", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func SetVolume(volume float64) {
|
||||
if MainPlayer.SetVolume(volume) != nil {
|
||||
l().Warnf("set mpv volume to %f failed", volume)
|
||||
return
|
||||
}
|
||||
config.Player.Volume = volume
|
||||
}
|
||||
|
||||
func Destroy() {
|
||||
MainPlayer.Stop()
|
||||
}
|
||||
|
||||
func GetAudioDevices() []player.AudioDevice {
|
||||
dl, err := MainPlayer.GetAudioDeviceList()
|
||||
if err != nil {
|
||||
return make([]player.AudioDevice, 0)
|
||||
}
|
||||
return dl
|
||||
}
|
||||
|
||||
func SetAudioDevice(device string) {
|
||||
l().Infof("set audio device to %s", device)
|
||||
if err := MainPlayer.SetAudioDevice(device); err != nil {
|
||||
l().Warnf("set mpv audio device to %s failed, %s", device, err)
|
||||
MainPlayer.SetAudioDevice("auto")
|
||||
config.Player.AudioDevice = "auto"
|
||||
return
|
||||
}
|
||||
config.Player.AudioDevice = device
|
||||
}
|
||||
@@ -1,22 +1,34 @@
|
||||
package controller
|
||||
|
||||
import "AynaLivePlayer/player"
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/model"
|
||||
)
|
||||
|
||||
func AddToHistory(media *player.Media) {
|
||||
l().Tracef("add media %s (%s) to history", media.Title, media.Artist)
|
||||
media = media.Copy()
|
||||
History.Push(media)
|
||||
return
|
||||
type IPlaylistController interface {
|
||||
Size() int
|
||||
GetHistory() IPlaylist
|
||||
AddToHistory(media *model.Media)
|
||||
GetDefault() IPlaylist
|
||||
GetCurrent() IPlaylist
|
||||
Get(index int) IPlaylist
|
||||
Add(pname string, uri string) IPlaylist
|
||||
Remove(index int) IPlaylist
|
||||
SetDefault(index int) error
|
||||
PreparePlaylistByIndex(index int) error
|
||||
}
|
||||
|
||||
func ToHistoryMedia(media *player.Media) *player.Media {
|
||||
media = media.Copy()
|
||||
media.User = HistoryUser
|
||||
return media
|
||||
}
|
||||
|
||||
func ToSystemMedia(media *player.Media) *player.Media {
|
||||
media = media.Copy()
|
||||
media.User = player.SystemUser
|
||||
return media
|
||||
type IPlaylist interface {
|
||||
Model() *model.Playlist // mutable model (not a copy)
|
||||
EventManager() *event.Manager
|
||||
Name() string
|
||||
Size() int
|
||||
Get(index int) *model.Media
|
||||
Pop() *model.Media
|
||||
Replace(medias []*model.Media)
|
||||
Push(media *model.Media)
|
||||
Insert(index int, media *model.Media)
|
||||
Delete(index int) *model.Media
|
||||
Move(src int, dst int)
|
||||
Next() *model.Media
|
||||
}
|
||||
|
||||
@@ -3,17 +3,11 @@ package controller
|
||||
type Plugin interface {
|
||||
Name() string
|
||||
Enable() error
|
||||
Disable() error
|
||||
}
|
||||
|
||||
func LoadPlugin(plugin Plugin) {
|
||||
l().Info("Loading plugin: " + plugin.Name())
|
||||
if err := plugin.Enable(); err != nil {
|
||||
l().Warnf("Failed to load plugin: %s, %s", plugin.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
func LoadPlugins(plugins ...Plugin) {
|
||||
for _, plugin := range plugins {
|
||||
LoadPlugin(plugin)
|
||||
}
|
||||
type IPluginController interface {
|
||||
LoadPlugin(plugin Plugin)
|
||||
LoadPlugins(plugins ...Plugin)
|
||||
ClosePlugins()
|
||||
}
|
||||
|
||||
@@ -1,77 +1,30 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/player"
|
||||
"AynaLivePlayer/provider"
|
||||
"AynaLivePlayer/model"
|
||||
)
|
||||
|
||||
func PrepareMedia(media *player.Media) error {
|
||||
var err error
|
||||
if media.Title == "" || media.Cover == "" {
|
||||
l().Trace("fetching media info")
|
||||
if err = provider.UpdateMedia(media); err != nil {
|
||||
l().Warn("fail to prepare media when fetch info", 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)
|
||||
}
|
||||
var PlaylistUser = &model.User{Name: "Playlists"}
|
||||
var SystemUser = &model.User{Name: "System"}
|
||||
var HistoryUser = &model.User{Name: "History"}
|
||||
|
||||
}
|
||||
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
|
||||
type IProviderController interface {
|
||||
GetPriority() []string
|
||||
PrepareMedia(media *model.Media) error
|
||||
MediaMatch(keyword string) *model.Media
|
||||
Search(keyword string) ([]*model.Media, error)
|
||||
SearchWithProvider(keyword string, provider string) ([]*model.Media, error)
|
||||
PreparePlaylist(playlist IPlaylist) error
|
||||
}
|
||||
|
||||
func Search(keyword string) ([]*player.Media, error) {
|
||||
l().Infof("Search for %s", keyword)
|
||||
for _, p := range config.Provider.Priority {
|
||||
if pr, ok := provider.Providers[p]; ok {
|
||||
r, err := pr.Search(keyword)
|
||||
if err != nil {
|
||||
l().Warn("Provider %s return err", err)
|
||||
continue
|
||||
}
|
||||
return r, err
|
||||
} else {
|
||||
l().Warnf("Provider %s not exist", p)
|
||||
}
|
||||
}
|
||||
return nil, provider.ErrorNoSuchProvider
|
||||
}
|
||||
|
||||
func SearchWithProvider(keyword string, p string) ([]*player.Media, error) {
|
||||
l().Infof("Search for %s using %s", keyword, p)
|
||||
if pr, ok := provider.Providers[p]; ok {
|
||||
r, err := pr.Search(keyword)
|
||||
return r, err
|
||||
}
|
||||
l().Warnf("Provider %s not exist", p)
|
||||
return nil, provider.ErrorNoSuchProvider
|
||||
}
|
||||
|
||||
func ApplyUser(medias []*player.Media, user interface{}) {
|
||||
func ApplyUser(medias []*model.Media, user interface{}) {
|
||||
for _, m := range medias {
|
||||
m.User = user
|
||||
}
|
||||
}
|
||||
|
||||
func PreparePlaylist(playlist *player.Playlist) error {
|
||||
l().Debug("Prepare playlist ", playlist.Meta.(provider.Meta))
|
||||
medias, err := provider.GetPlaylist(playlist.Meta.(provider.Meta))
|
||||
if err != nil {
|
||||
l().Warn("prepare playlist failed ", err)
|
||||
return err
|
||||
}
|
||||
ApplyUser(medias, player.SystemUser)
|
||||
playlist.Replace(medias)
|
||||
return nil
|
||||
func ToSpMedia(media *model.Media, user *model.User) *model.Media {
|
||||
media = media.Copy()
|
||||
media.User = user
|
||||
return media
|
||||
}
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/logger"
|
||||
"github.com/sirupsen/logrus"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type EventId string
|
||||
|
||||
const MODULE_HANDLER = "EventHandler"
|
||||
|
||||
var eventLogger = logger.Logger.WithFields(logrus.Fields{
|
||||
"Module": MODULE_HANDLER,
|
||||
})
|
||||
|
||||
type Event struct {
|
||||
Id EventId
|
||||
Cancelled bool
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
type EventHandlerFunc func(event *Event)
|
||||
|
||||
type EventHandler struct {
|
||||
EventId EventId
|
||||
Name string
|
||||
Handler EventHandlerFunc
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
handlers map[string]*EventHandler
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func NewHandler() *Handler {
|
||||
return &Handler{
|
||||
handlers: make(map[string]*EventHandler),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) Register(handler *EventHandler) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
eventLogger.Tracef("register new handler id=%s,name=%s", handler.EventId, handler.Name)
|
||||
h.handlers[handler.Name] = handler
|
||||
}
|
||||
|
||||
func (h *Handler) RegisterA(id EventId, name string, handler EventHandlerFunc) {
|
||||
h.Register(&EventHandler{
|
||||
EventId: id,
|
||||
Name: name,
|
||||
Handler: handler,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) UnregisterAll() {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
eventLogger.Trace("clear all handler")
|
||||
h.handlers = make(map[string]*EventHandler)
|
||||
}
|
||||
|
||||
func (h *Handler) Unregister(name string) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
eventLogger.Tracef("unregister handler name=%s", name)
|
||||
delete(h.handlers, name)
|
||||
}
|
||||
|
||||
func (h *Handler) Call(event *Event) {
|
||||
h.lock.RLock()
|
||||
defer h.lock.RUnlock()
|
||||
for _, eh := range h.handlers {
|
||||
if eh.EventId == event.Id {
|
||||
eventLogger.Tracef("handler name=%s called by event_id = %s", event.Id, eh.Name)
|
||||
// todo: @3
|
||||
go eh.Handler(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) CallA(id EventId, data interface{}) {
|
||||
h.Call(&Event{
|
||||
Id: id,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
72
go.mod
72
go.mod
@@ -1,35 +1,61 @@
|
||||
module AynaLivePlayer
|
||||
|
||||
go 1.16
|
||||
go 1.19
|
||||
|
||||
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
|
||||
fyne.io/fyne/v2 v2.2.4
|
||||
github.com/XiaoMengXinX/Music163Api-Go v0.1.29
|
||||
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/aynakeya/blivedm v0.1.6
|
||||
github.com/aynakeya/go-mpv v0.0.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/magiconair/properties v1.8.5
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
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/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/spf13/cast v1.5.0
|
||||
github.com/stretchr/testify v1.7.2
|
||||
github.com/tidwall/gjson v1.14.3
|
||||
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459
|
||||
golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15
|
||||
gopkg.in/ini.v1 v1.66.4
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/aynakeya/blivedm => D:\Repository\blivedm
|
||||
github.com/aynakeya/go-mpv => D:\Repository\go-mpv
|
||||
require (
|
||||
fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect
|
||||
github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 // indirect
|
||||
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect
|
||||
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gopherjs/gopherjs v1.17.2 // indirect
|
||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect
|
||||
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect
|
||||
github.com/tevino/abool v1.2.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/yuin/goldmark v1.4.0 // indirect
|
||||
golang.org/x/image v0.0.0-20220601225756-64ec528b34cd // indirect
|
||||
golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee // indirect
|
||||
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 // indirect
|
||||
)
|
||||
|
||||
//replace (
|
||||
// github.com/aynakeya/blivedm => D:\Repository\blivedm
|
||||
// github.com/aynakeya/go-mpv => D:\Repository\go-mpv
|
||||
//)
|
||||
|
||||
670
go.sum
670
go.sum
@@ -1,139 +1,703 @@
|
||||
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=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
fyne.io/fyne/v2 v2.2.4 h1:izyiDUjJYAB7B/MST7M9GDs+mQ0CwDgRZTiVJZQoEe4=
|
||||
fyne.io/fyne/v2 v2.2.4/go.mod h1:MBoGuHzLLSXdQOWFAwWhIhYTEMp33zqtGCReSWhaQTA=
|
||||
fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93 h1:V2IC9t0Zj9Ur6qDbfhUuzVmIvXKFyxZXRJyigUvovs4=
|
||||
fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE=
|
||||
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/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/XiaoMengXinX/Music163Api-Go v0.1.29 h1:c7ekfgo4qgEJ3Wjm9rMhGm7ggN8XqbD1idQka4unJ+Q=
|
||||
github.com/XiaoMengXinX/Music163Api-Go v0.1.29/go.mod h1:kLU/CkLxKnEJFCge0URvQ0lHt6ImoG1/2aVeNbgV2RQ=
|
||||
github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
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/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aynakeya/blivedm v0.1.6 h1:fnjyyHYnXAArcLqMRNLQCAdvRFiEVGA/a/54UatZF0k=
|
||||
github.com/aynakeya/blivedm v0.1.6/go.mod h1:g7cA6/BfDcrsD4v9w+P6B9Z+gANi4jPlGkZ1Oyuu/i4=
|
||||
github.com/aynakeya/go-mpv v0.0.6 h1:WCBwHrzl700C1J3f+aXR+URw/OKYPjwUjDW9diOsXYY=
|
||||
github.com/aynakeya/go-mpv v0.0.6/go.mod h1:do6ImaEyt9dlQ7JRS/8ke+P9q4kGW8+Bf6j3faBQOfE=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
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/tag v0.0.0-20220618230019-adf36e896086 h1:ORubSQoKnncsBnR4zD9CuYFJCPOCuSNEpWEZrDdBXkc=
|
||||
github.com/dhowden/tag v0.0.0-20220618230019-adf36e896086/go.mod h1:Z3Lomva4pyMWYezjMAU5QWRh0p1VvO4199OHlFnyKkM=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
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=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f h1:s0O46d8fPwk9kU4k1jj76wBquMVETx7uveQD9MCIQoU=
|
||||
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/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe h1:A/wiwvQ0CAjPkuJytaD+SsXkPU0asQ+guQEIg1BJGX4=
|
||||
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg=
|
||||
github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 h1:+31CdF/okdokeFNoy9L/2PccG3JFidQT3ev64/r4pYU=
|
||||
github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504/go.mod h1:gLRWYfYnMA9TONeppRSikMdXlHQ97xVsPojddUv3b/E=
|
||||
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 h1:hnLq+55b7Zh7/2IRzWCpiTcAvjv/P8ERF+N7+xXbZhk=
|
||||
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2/go.mod h1:eO7W361vmlPOrykIg+Rsh1SZ3tQBaOsfzZhsIOb/Lm0=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk=
|
||||
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec h1:3FLiRYO6PlQFDpUU7OEFlWgjGD1jnBIVSJ5SYRWk+9c=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
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=
|
||||
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
|
||||
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
|
||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20211219123610-ec9572f70e60/go.mod h1:cz9oNYuRUWGdHmLF2IodMLkAhcPtXeULvcBNagUrxTI=
|
||||
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
||||
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/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
|
||||
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/goxjs/gl v0.0.0-20210104184919-e3fafc6f8f2a/go.mod h1:dy/f2gjY09hwVfIyATps4G2ai7/hLwLkc5TrPqONuXY=
|
||||
github.com/goxjs/glfw v0.0.0-20191126052801-d2efb5f20838/go.mod h1:oS8P8gVOT4ywTcjV6wZlOU4GuVFQ8F5328KY3MJ79CY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI=
|
||||
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/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed6yhQtAU34Kx3Qq2wwBzGFKY8zKk=
|
||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
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/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
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/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
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/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
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/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
|
||||
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM=
|
||||
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
|
||||
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM=
|
||||
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
|
||||
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
|
||||
github.com/tidwall/gjson v1.8.0/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
|
||||
github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo=
|
||||
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
|
||||
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
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/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/urfave/cli/v2 v2.4.0/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg=
|
||||
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.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
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=
|
||||
github.com/yuin/goldmark v1.4.0 h1:OtISOGfH6sOWa1/qXqqAiOIAO6Z5J3AEAE18WAq6BiQ=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
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/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 h1:5oN1Pz/eDhCpbMbLstvIPa0b/BEQo6g6nwV3pLjfM6w=
|
||||
golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20220601225756-64ec528b34cd h1:9NbNcTg//wfC5JskFW4Z3sqwVnjmJKHxLAol1bW2qgw=
|
||||
golang.org/x/image v0.0.0-20220601225756-64ec528b34cd/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee h1:/tShaw8UTf0XzI8DOZwQHzC7d6Vi3EtrBnftiZ4vAvU=
|
||||
golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb h1:pirldcYWx7rx7kE5r+9WsOXPXK0+WH5+uZ7uPmJ44uM=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b h1:tvrvnPFcdzp294diPnrdZZZ8XUt2Tyj7svb7X52iDuU=
|
||||
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
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.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
||||
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
|
||||
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 h1:oomkgU6VaQDsV6qZby2uz1Lap0eXmku8+2em3A/l700=
|
||||
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
||||
43
gui/component/button.go
Normal file
43
gui/component/button.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type AsyncButton struct {
|
||||
widget.Button
|
||||
}
|
||||
|
||||
func NewAsyncButton(label string, tapped func()) *AsyncButton {
|
||||
b := &AsyncButton{
|
||||
Button: widget.Button{
|
||||
Text: label,
|
||||
},
|
||||
}
|
||||
b.ExtendBaseWidget(b)
|
||||
b.SetOnTapped(tapped)
|
||||
return b
|
||||
}
|
||||
|
||||
func NewAsyncButtonWithIcon(label string, icon fyne.Resource, tapped func()) *AsyncButton {
|
||||
b := &AsyncButton{
|
||||
Button: widget.Button{
|
||||
Text: label,
|
||||
Icon: icon,
|
||||
},
|
||||
}
|
||||
b.ExtendBaseWidget(b)
|
||||
b.SetOnTapped(tapped)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *AsyncButton) SetOnTapped(f func()) {
|
||||
b.Button.OnTapped = func() {
|
||||
b.Disable()
|
||||
go func() {
|
||||
f()
|
||||
b.Enable()
|
||||
}()
|
||||
}
|
||||
}
|
||||
32
gui/component/entry.go
Normal file
32
gui/component/entry.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type Entry struct {
|
||||
widget.Entry
|
||||
OnKeyUp func(key *fyne.KeyEvent)
|
||||
OnKeyDown func(key *fyne.KeyEvent)
|
||||
}
|
||||
|
||||
func NewEntry() *Entry {
|
||||
e := &Entry{}
|
||||
e.ExtendBaseWidget(e)
|
||||
return e
|
||||
}
|
||||
|
||||
func (m *Entry) KeyUp(key *fyne.KeyEvent) {
|
||||
m.Entry.KeyUp(key)
|
||||
if m.OnKeyUp != nil {
|
||||
m.OnKeyUp(key)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Entry) KeyDown(key *fyne.KeyEvent) {
|
||||
m.Entry.KeyDown(key)
|
||||
if m.OnKeyDown != nil {
|
||||
m.OnKeyDown(key)
|
||||
}
|
||||
}
|
||||
38
gui/component/slider.go
Normal file
38
gui/component/slider.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type SliderPlus struct {
|
||||
widget.Slider
|
||||
OnDragEnd func(value float64)
|
||||
Dragging bool // during dragging
|
||||
}
|
||||
|
||||
func NewSliderPlus(min, max float64) *SliderPlus {
|
||||
slider := &SliderPlus{
|
||||
Slider: widget.Slider{
|
||||
Value: 0,
|
||||
Min: min,
|
||||
Max: max,
|
||||
Step: 1,
|
||||
Orientation: widget.Horizontal,
|
||||
},
|
||||
}
|
||||
slider.ExtendBaseWidget(slider)
|
||||
return slider
|
||||
}
|
||||
|
||||
func (s *SliderPlus) DragEnd() {
|
||||
if s.OnDragEnd != nil {
|
||||
s.OnDragEnd(s.Value)
|
||||
}
|
||||
s.Dragging = false
|
||||
}
|
||||
|
||||
func (s *SliderPlus) Dragged(e *fyne.DragEvent) {
|
||||
s.Dragging = true
|
||||
s.Slider.Dragged(e)
|
||||
}
|
||||
166
gui/component/split.go
Normal file
166
gui/component/split.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type FixedSplit struct {
|
||||
widget.BaseWidget
|
||||
Offset float64
|
||||
Horizontal bool
|
||||
SeparatorThickness float32
|
||||
Leading fyne.CanvasObject
|
||||
Trailing fyne.CanvasObject
|
||||
}
|
||||
|
||||
func NewFixedHSplitContainer(leading, trailing fyne.CanvasObject, offset float64) *FixedSplit {
|
||||
return NewFixedSplitContainer(leading, trailing, true, offset)
|
||||
|
||||
}
|
||||
|
||||
func NewFixedVSplitContainer(top, bottom fyne.CanvasObject, offset float64) *FixedSplit {
|
||||
return NewFixedSplitContainer(top, bottom, false, offset)
|
||||
}
|
||||
|
||||
func NewFixedSplitContainer(leading, trailing fyne.CanvasObject, horizontal bool, offset float64) *FixedSplit {
|
||||
s := &FixedSplit{
|
||||
Offset: offset, // Sensible default, can be overridden with SetOffset
|
||||
SeparatorThickness: theme.SeparatorThicknessSize(),
|
||||
Horizontal: horizontal,
|
||||
Leading: leading,
|
||||
Trailing: trailing,
|
||||
}
|
||||
s.BaseWidget.ExtendBaseWidget(s)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *FixedSplit) CreateRenderer() fyne.WidgetRenderer {
|
||||
s.BaseWidget.ExtendBaseWidget(s)
|
||||
d := widget.NewSeparator()
|
||||
return &fixedSplitContainerRenderer{
|
||||
split: s,
|
||||
divider: d,
|
||||
objects: []fyne.CanvasObject{s.Leading, d, s.Trailing},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FixedSplit) SetOffset(offset float64) {
|
||||
if s.Offset == offset {
|
||||
return
|
||||
}
|
||||
s.Offset = offset
|
||||
s.Refresh()
|
||||
}
|
||||
|
||||
func (s *FixedSplit) SetSepThickness(thickness float32) {
|
||||
if s.SeparatorThickness == thickness {
|
||||
return
|
||||
}
|
||||
s.SeparatorThickness = thickness
|
||||
s.Refresh()
|
||||
}
|
||||
|
||||
type fixedSplitContainerRenderer struct {
|
||||
split *FixedSplit
|
||||
divider *widget.Separator
|
||||
objects []fyne.CanvasObject
|
||||
}
|
||||
|
||||
func (r *fixedSplitContainerRenderer) Destroy() {
|
||||
}
|
||||
|
||||
func (r *fixedSplitContainerRenderer) Layout(size fyne.Size) {
|
||||
var dividerPos, leadingPos, trailingPos fyne.Position
|
||||
var dividerSize, leadingSize, trailingSize fyne.Size
|
||||
|
||||
if r.split.Horizontal {
|
||||
lw, tw := r.computeSplitLengths(size.Width, r.split.Leading.MinSize().Width, r.split.Trailing.MinSize().Width)
|
||||
leadingPos.X = 0
|
||||
leadingSize.Width = lw
|
||||
leadingSize.Height = size.Height
|
||||
dividerPos.X = lw
|
||||
dividerSize.Width = r.split.SeparatorThickness
|
||||
dividerSize.Height = size.Height
|
||||
trailingPos.X = lw + dividerSize.Width
|
||||
trailingSize.Width = tw
|
||||
trailingSize.Height = size.Height
|
||||
} else {
|
||||
lh, th := r.computeSplitLengths(size.Height, r.split.Leading.MinSize().Height, r.split.Trailing.MinSize().Height)
|
||||
leadingPos.Y = 0
|
||||
leadingSize.Width = size.Width
|
||||
leadingSize.Height = lh
|
||||
dividerPos.Y = lh
|
||||
dividerSize.Width = size.Width
|
||||
dividerSize.Height = r.split.SeparatorThickness
|
||||
trailingPos.Y = lh + dividerSize.Height
|
||||
trailingSize.Width = size.Width
|
||||
trailingSize.Height = th
|
||||
}
|
||||
|
||||
r.divider.Move(dividerPos)
|
||||
r.divider.Resize(dividerSize)
|
||||
r.split.Leading.Move(leadingPos)
|
||||
r.split.Leading.Resize(leadingSize)
|
||||
r.split.Trailing.Move(trailingPos)
|
||||
r.split.Trailing.Resize(trailingSize)
|
||||
canvas.Refresh(r.divider)
|
||||
canvas.Refresh(r.split.Leading)
|
||||
canvas.Refresh(r.split.Trailing)
|
||||
}
|
||||
|
||||
func (r *fixedSplitContainerRenderer) MinSize() fyne.Size {
|
||||
s := fyne.NewSize(0, 0)
|
||||
for _, o := range r.objects {
|
||||
min := o.MinSize()
|
||||
if r.split.Horizontal {
|
||||
s.Width += min.Width
|
||||
s.Height = fyne.Max(s.Height, min.Height)
|
||||
} else {
|
||||
s.Width = fyne.Max(s.Width, min.Width)
|
||||
s.Height += min.Height
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (r *fixedSplitContainerRenderer) Objects() []fyne.CanvasObject {
|
||||
return r.objects
|
||||
}
|
||||
|
||||
func (r *fixedSplitContainerRenderer) Refresh() {
|
||||
r.objects[0] = r.split.Leading
|
||||
// [1] is divider which doesn't change
|
||||
r.objects[2] = r.split.Trailing
|
||||
r.Layout(r.split.Size())
|
||||
canvas.Refresh(r.split)
|
||||
}
|
||||
|
||||
func (r *fixedSplitContainerRenderer) computeSplitLengths(total, lMin, tMin float32) (float32, float32) {
|
||||
available := float64(total - r.split.SeparatorThickness)
|
||||
if available <= 0 {
|
||||
return 0, 0
|
||||
}
|
||||
ld := float64(lMin)
|
||||
tr := float64(tMin)
|
||||
offset := r.split.Offset
|
||||
|
||||
min := ld / available
|
||||
max := 1 - tr/available
|
||||
if min <= max {
|
||||
if offset < min {
|
||||
offset = min
|
||||
}
|
||||
if offset > max {
|
||||
offset = max
|
||||
}
|
||||
} else {
|
||||
offset = ld / (ld + tr)
|
||||
}
|
||||
|
||||
ld = offset * available
|
||||
tr = available - ld
|
||||
return float32(ld), float32(tr)
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/common/i18n"
|
||||
"AynaLivePlayer/controller"
|
||||
"AynaLivePlayer/i18n"
|
||||
"AynaLivePlayer/model"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/data/binding"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
@@ -26,16 +25,33 @@ func (b *bascicConfig) CreatePanel() fyne.CanvasObject {
|
||||
if b.panel != nil {
|
||||
return b.panel
|
||||
}
|
||||
|
||||
randomPlaylist := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("gui.config.basic.random_playlist")),
|
||||
widget.NewCheckWithData(
|
||||
newCheckInit(
|
||||
i18n.T("gui.config.basic.random_playlist.user"),
|
||||
binding.BindBool(&controller.UserPlaylist.Config.RandomNext)),
|
||||
widget.NewCheckWithData(
|
||||
func(b bool) {
|
||||
l().Infof("Set random playlist for user: %t", b)
|
||||
if b {
|
||||
controller.Instance.Playlists().GetCurrent().Model().Mode = model.PlaylistModeRandom
|
||||
} else {
|
||||
controller.Instance.Playlists().GetCurrent().Model().Mode = model.PlaylistModeNormal
|
||||
}
|
||||
},
|
||||
controller.Instance.Playlists().GetCurrent().Model().Mode == model.PlaylistModeRandom),
|
||||
newCheckInit(
|
||||
i18n.T("gui.config.basic.random_playlist.system"),
|
||||
binding.BindBool(&controller.SystemPlaylist.Config.RandomNext)),
|
||||
func(b bool) {
|
||||
l().Infof("Set random playlist for system: %t", b)
|
||||
if b {
|
||||
controller.Instance.Playlists().GetDefault().Model().Mode = model.PlaylistModeRandom
|
||||
} else {
|
||||
controller.Instance.Playlists().GetDefault().Model().Mode = model.PlaylistModeNormal
|
||||
}
|
||||
},
|
||||
controller.Instance.Playlists().GetDefault().Model().Mode == model.PlaylistModeRandom),
|
||||
)
|
||||
devices := controller.GetAudioDevices()
|
||||
devices := controller.Instance.PlayControl().GetAudioDevices()
|
||||
deviceDesc := make([]string, len(devices))
|
||||
deviceDesc2Name := make(map[string]string)
|
||||
for i, device := range devices {
|
||||
@@ -43,17 +59,20 @@ func (b *bascicConfig) CreatePanel() fyne.CanvasObject {
|
||||
deviceDesc2Name[device.Description] = device.Name
|
||||
}
|
||||
deviceSel := widget.NewSelect(deviceDesc, func(s string) {
|
||||
controller.SetAudioDevice(deviceDesc2Name[s])
|
||||
controller.Instance.PlayControl().SetAudioDevice(deviceDesc2Name[s])
|
||||
})
|
||||
deviceSel.Selected = config.Player.AudioDevice
|
||||
deviceSel.Selected = controller.Instance.PlayControl().GetCurrentAudioDevice()
|
||||
outputDevice := container.NewBorder(nil, nil,
|
||||
widget.NewLabel(i18n.T("gui.config.basic.audio_device")), nil,
|
||||
deviceSel)
|
||||
skipPlaylist := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("gui.config.basic.skip_playlist")),
|
||||
widget.NewCheckWithData(
|
||||
newCheckInit(
|
||||
i18n.T("gui.config.basic.skip_playlist.prompt"),
|
||||
binding.BindBool(&config.Player.SkipPlaylist),
|
||||
func(b bool) {
|
||||
controller.Instance.PlayControl().SetSkipPlaylist(b)
|
||||
},
|
||||
controller.Instance.PlayControl().GetSkipPlaylist(),
|
||||
),
|
||||
)
|
||||
b.panel = container.NewVBox(randomPlaylist, outputDevice, skipPlaylist)
|
||||
|
||||
@@ -1,34 +1,24 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/gui/component"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type TestConfig struct {
|
||||
}
|
||||
|
||||
func (t *TestConfig) Title() string {
|
||||
return "Test Title"
|
||||
}
|
||||
|
||||
func (T *TestConfig) Description() string {
|
||||
return "Test Description"
|
||||
}
|
||||
|
||||
func (t *TestConfig) CreatePanel() fyne.CanvasObject {
|
||||
return widget.NewLabel("asdf")
|
||||
}
|
||||
|
||||
func createConfigLayout() fyne.CanvasObject {
|
||||
// initialize config panels
|
||||
for _, c := range ConfigList {
|
||||
c.CreatePanel()
|
||||
}
|
||||
content := container.NewMax()
|
||||
entryList := widget.NewList(
|
||||
func() int {
|
||||
return len(ConfigList)
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
return widget.NewLabel("AAAAAAAAAAAAAAAA")
|
||||
return widget.NewLabel("")
|
||||
},
|
||||
func(id widget.ListItemID, object fyne.CanvasObject) {
|
||||
object.(*widget.Label).SetText(ConfigList[id].Title())
|
||||
@@ -40,16 +30,11 @@ func createConfigLayout() fyne.CanvasObject {
|
||||
seg.Style.Alignment = fyne.TextAlignCenter
|
||||
}
|
||||
}
|
||||
a := container.NewVScroll(ConfigList[id].CreatePanel())
|
||||
content.Objects = []fyne.CanvasObject{
|
||||
container.NewBorder(container.NewVBox(desc, widget.NewSeparator()), nil, nil, nil,
|
||||
a),
|
||||
container.NewVScroll(container.NewVBox(desc, widget.NewSeparator(), ConfigList[id].CreatePanel())),
|
||||
}
|
||||
|
||||
content.Refresh()
|
||||
}
|
||||
return container.NewBorder(
|
||||
nil, nil,
|
||||
container.NewHBox(entryList, widget.NewSeparator()), nil,
|
||||
content)
|
||||
|
||||
return component.NewFixedSplitContainer(entryList, content, true, 0.23)
|
||||
}
|
||||
|
||||
49
gui/gui.go
49
gui/gui.go
@@ -1,67 +1,74 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/i18n"
|
||||
"AynaLivePlayer/common/logger"
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/i18n"
|
||||
"AynaLivePlayer/logger"
|
||||
"AynaLivePlayer/controller"
|
||||
"AynaLivePlayer/resource"
|
||||
"fmt"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/app"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/driver/desktop"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"github.com/sirupsen/logrus"
|
||||
"os"
|
||||
)
|
||||
|
||||
const MODULE_GUI = "GUI"
|
||||
|
||||
type ConfigLayout interface {
|
||||
Title() string
|
||||
Description() string
|
||||
CreatePanel() fyne.CanvasObject
|
||||
}
|
||||
|
||||
var App fyne.App
|
||||
var MainWindow fyne.Window
|
||||
var ConfigList = []ConfigLayout{&bascicConfig{}}
|
||||
|
||||
func l() *logrus.Entry {
|
||||
return logger.Logger.WithField("Module", MODULE_GUI)
|
||||
}
|
||||
|
||||
func black_magic() {
|
||||
widget.RichTextStyleStrong.TextStyle.Bold = false
|
||||
}
|
||||
|
||||
func Initialize() {
|
||||
os.Setenv("FYNE_FONT", config.GetAssetPath("msyh.ttc"))
|
||||
//black_magic()
|
||||
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))
|
||||
App.Settings().SetTheme(&myTheme{})
|
||||
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()),
|
||||
container.NewBorder(nil, createPlayControllerV2(), nil, nil, createPlaylist()),
|
||||
),
|
||||
container.NewTabItem(i18n.T("gui.tab.search"),
|
||||
newPaddedBoarder(createSearchBar(), nil, nil, nil, createSearchList()),
|
||||
container.NewBorder(createSearchBar(), nil, nil, nil, createSearchList()),
|
||||
),
|
||||
container.NewTabItem(i18n.T("gui.tab.room"),
|
||||
newPaddedBoarder(createRoomController(), nil, nil, nil, createRoomLogger()),
|
||||
container.NewBorder(nil, nil, createRoomSelector(), nil, createRoomController()),
|
||||
),
|
||||
container.NewTabItem(i18n.T("gui.tab.playlist"),
|
||||
newPaddedBoarder(nil, nil, createPlaylists(), nil, createPlaylistMedias()),
|
||||
container.NewBorder(nil, nil, createPlaylists(), nil, createPlaylistMedias()),
|
||||
),
|
||||
container.NewTabItem(i18n.T("gui.tab.history"),
|
||||
newPaddedBoarder(nil, nil, nil, nil, createHistoryList()),
|
||||
container.NewBorder(nil, nil, nil, nil, createHistoryList()),
|
||||
),
|
||||
container.NewTabItem(i18n.T("gui.tab.config"),
|
||||
newPaddedBoarder(nil, nil, nil, nil, createConfigLayout()),
|
||||
createConfigLayout(),
|
||||
),
|
||||
)
|
||||
|
||||
tabs.SetTabLocation(container.TabLocationTop)
|
||||
|
||||
MainWindow.SetIcon(resource.ImageIcon)
|
||||
MainWindow.SetContent(tabs)
|
||||
//MainWindow.Resize(fyne.NewSize(1280, 720))
|
||||
MainWindow.Resize(fyne.NewSize(960, 480))
|
||||
//MainWindow.SetFixedSize(true)
|
||||
}
|
||||
|
||||
func AddConfigLayout(cfgs ...ConfigLayout) {
|
||||
ConfigList = append(ConfigList, cfgs...)
|
||||
func addShortCut() {
|
||||
key := &desktop.CustomShortcut{KeyName: fyne.KeyRight, Modifier: fyne.KeyModifierControl | fyne.KeyModifierShift}
|
||||
MainWindow.Canvas().AddShortcut(key, func(shortcut fyne.Shortcut) {
|
||||
l().Info("Shortcut pressed: Ctrl+Shift+Right")
|
||||
controller.Instance.PlayControl().PlayNext()
|
||||
})
|
||||
}
|
||||
|
||||
56
gui/gutil/resize.go
Normal file
56
gui/gutil/resize.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package gutil
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/model"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/storage"
|
||||
"github.com/nfnt/resize"
|
||||
"image"
|
||||
"image/png"
|
||||
)
|
||||
|
||||
func ResizeImage(resource fyne.Resource, width int, height int) fyne.Resource {
|
||||
data := resource.Content()
|
||||
img, _, err := image.Decode(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return resource
|
||||
}
|
||||
img = resize.Thumbnail(uint(width), uint(height), img, resize.Lanczos3)
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
err = png.Encode(buf, img)
|
||||
if err != nil {
|
||||
return resource
|
||||
}
|
||||
return fyne.NewStaticResource(resource.Name(), buf.Bytes())
|
||||
}
|
||||
|
||||
func NewImageFromPlayerPicture(picture model.Picture) (*canvas.Image, error) {
|
||||
var img *canvas.Image
|
||||
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")
|
||||
}
|
||||
|
||||
} 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")
|
||||
}
|
||||
}
|
||||
// compress image, so it won't be too large
|
||||
img.Resource = ResizeImage(img.Resource, 128, 128)
|
||||
return img, nil
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package gui
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
@@ -17,21 +18,6 @@ func newLabelWithWrapping(text string, wrapping fyne.TextWrap) *widget.Label {
|
||||
return w
|
||||
}
|
||||
|
||||
func createAsyncOnTapped(btn *widget.Button, f func()) func() {
|
||||
return func() {
|
||||
btn.Disable()
|
||||
go func() {
|
||||
f()
|
||||
btn.Enable()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func createAsyncButton(btn *widget.Button, tapped func()) *widget.Button {
|
||||
btn.OnTapped = createAsyncOnTapped(btn, tapped)
|
||||
return btn
|
||||
}
|
||||
|
||||
type ContextMenuButton struct {
|
||||
widget.Button
|
||||
menu *fyne.Menu
|
||||
@@ -49,28 +35,14 @@ func newContextMenuButton(label string, menu *fyne.Menu) *ContextMenuButton {
|
||||
return b
|
||||
}
|
||||
|
||||
type FixedSplitContainer struct {
|
||||
*container.Split
|
||||
}
|
||||
|
||||
func (f *FixedSplitContainer) Dragged(event *fyne.DragEvent) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
func (f *FixedSplitContainer) DragEnd() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
func newFixedSplitContainer(horizontal bool, leading, trailing fyne.CanvasObject) *FixedSplitContainer {
|
||||
s := &container.Split{
|
||||
Offset: 0.5, // Sensible default, can be overridden with SetOffset
|
||||
Horizontal: horizontal,
|
||||
Leading: leading,
|
||||
Trailing: trailing,
|
||||
func showDialogIfError(err error) {
|
||||
if err != nil {
|
||||
dialog.ShowError(err, MainWindow)
|
||||
}
|
||||
fs := &FixedSplitContainer{
|
||||
s,
|
||||
}
|
||||
fs.Split.BaseWidget.ExtendBaseWidget(s)
|
||||
return fs
|
||||
}
|
||||
|
||||
func newCheckInit(name string, changed func(bool), checked bool) *widget.Check {
|
||||
check := widget.NewCheck(name, changed)
|
||||
check.SetChecked(checked)
|
||||
return check
|
||||
}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/common/i18n"
|
||||
"AynaLivePlayer/controller"
|
||||
"AynaLivePlayer/event"
|
||||
"AynaLivePlayer/i18n"
|
||||
"AynaLivePlayer/player"
|
||||
"AynaLivePlayer/model"
|
||||
"fmt"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type HistoryContainer struct {
|
||||
Playlist *player.Playlist
|
||||
var History = &struct {
|
||||
Playlist *model.Playlist
|
||||
List *widget.List
|
||||
}
|
||||
|
||||
var History = &PlaylistContainer{}
|
||||
mux sync.RWMutex
|
||||
}{}
|
||||
|
||||
func createHistoryList() fyne.CanvasObject {
|
||||
History.Playlist = controller.History
|
||||
History.Playlist = controller.Instance.Playlists().GetHistory().Model().Copy()
|
||||
History.List = widget.NewList(
|
||||
func() int {
|
||||
return History.Playlist.Size()
|
||||
@@ -38,7 +38,7 @@ func createHistoryList() fyne.CanvasObject {
|
||||
newLabelWithWrapping("user", fyne.TextTruncate)))
|
||||
},
|
||||
func(id widget.ListItemID, object fyne.CanvasObject) {
|
||||
m := History.Playlist.Playlist[History.Playlist.Size()-id-1]
|
||||
m := History.Playlist.Medias[History.Playlist.Size()-id-1].Copy()
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*widget.Label).SetText(
|
||||
m.Title)
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(
|
||||
@@ -47,11 +47,12 @@ func createHistoryList() fyne.CanvasObject {
|
||||
m.ToUser().Name)
|
||||
object.(*fyne.Container).Objects[1].(*widget.Label).SetText(fmt.Sprintf("%d", id))
|
||||
btns := object.(*fyne.Container).Objects[2].(*fyne.Container).Objects
|
||||
m.User = controller.HistoryUser
|
||||
btns[0].(*widget.Button).OnTapped = func() {
|
||||
controller.Play(controller.ToHistoryMedia(m))
|
||||
showDialogIfError(controller.Instance.PlayControl().Play(m))
|
||||
}
|
||||
btns[1].(*widget.Button).OnTapped = func() {
|
||||
controller.UserPlaylist.Push(controller.ToHistoryMedia(m))
|
||||
controller.Instance.Playlists().GetCurrent().Push(m)
|
||||
}
|
||||
})
|
||||
registerHistoryHandler()
|
||||
@@ -68,9 +69,10 @@ func createHistoryList() fyne.CanvasObject {
|
||||
}
|
||||
|
||||
func registerHistoryHandler() {
|
||||
History.Playlist.Handler.RegisterA(player.EventPlaylistUpdate, "gui.history.update", func(event *event.Event) {
|
||||
History.Playlist.Lock.RLock()
|
||||
controller.Instance.Playlists().GetHistory().EventManager().RegisterA(model.EventPlaylistUpdate, "gui.history.update", func(event *event.Event) {
|
||||
History.mux.RLock()
|
||||
History.Playlist = event.Data.(model.PlaylistUpdateEvent).Playlist
|
||||
History.List.Refresh()
|
||||
History.Playlist.Lock.RUnlock()
|
||||
History.mux.RUnlock()
|
||||
})
|
||||
}
|
||||
|
||||
15
gui/interface.go
Normal file
15
gui/interface.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package gui
|
||||
|
||||
import "fyne.io/fyne/v2"
|
||||
|
||||
var ConfigList = []ConfigLayout{&bascicConfig{}}
|
||||
|
||||
type ConfigLayout interface {
|
||||
Title() string
|
||||
Description() string
|
||||
CreatePanel() fyne.CanvasObject
|
||||
}
|
||||
|
||||
func AddConfigLayout(cfgs ...ConfigLayout) {
|
||||
ConfigList = append(ConfigList, cfgs...)
|
||||
}
|
||||
135
gui/liverooms.go
Normal file
135
gui/liverooms.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/common/i18n"
|
||||
"AynaLivePlayer/controller"
|
||||
"AynaLivePlayer/liveclient"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
var RoomTab = &struct {
|
||||
Rooms *widget.List
|
||||
Index int
|
||||
AddBtn *widget.Button
|
||||
RemoveBtn *widget.Button
|
||||
RoomTitle *widget.Label
|
||||
Status *widget.Label
|
||||
AutoConnect *widget.Check
|
||||
ConnectBtn *widget.Button
|
||||
DisConnectBtn *widget.Button
|
||||
}{}
|
||||
|
||||
func createRoomSelector() fyne.CanvasObject {
|
||||
RoomTab.Rooms = widget.NewList(
|
||||
func() int {
|
||||
return controller.Instance.LiveRooms().Size()
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
return widget.NewLabel("AAAAAAAAAAAAAAAA")
|
||||
},
|
||||
func(id widget.ListItemID, object fyne.CanvasObject) {
|
||||
object.(*widget.Label).SetText(
|
||||
controller.Instance.LiveRooms().Get(id).Title())
|
||||
})
|
||||
RoomTab.AddBtn = widget.NewButton(i18n.T("gui.room.button.add"), func() {
|
||||
clientNameEntry := widget.NewSelect(liveclient.GetAllClientNames(), nil)
|
||||
idEntry := widget.NewEntry()
|
||||
dia := dialog.NewCustomConfirm(
|
||||
i18n.T("gui.room.add.title"),
|
||||
i18n.T("gui.room.add.confirm"),
|
||||
i18n.T("gui.room.add.cancel"),
|
||||
container.NewVBox(
|
||||
container.New(
|
||||
layout.NewFormLayout(),
|
||||
widget.NewLabel(i18n.T("gui.room.add.client_name")),
|
||||
clientNameEntry,
|
||||
widget.NewLabel(i18n.T("gui.room.add.id_url")),
|
||||
idEntry,
|
||||
),
|
||||
widget.NewLabel(i18n.T("gui.room.add.prompt")),
|
||||
),
|
||||
func(b bool) {
|
||||
if b && len(clientNameEntry.Selected) > 0 && len(idEntry.Text) > 0 {
|
||||
_, err := controller.Instance.LiveRooms().AddRoom(clientNameEntry.Selected, idEntry.Text)
|
||||
showDialogIfError(err)
|
||||
RoomTab.Rooms.Refresh()
|
||||
}
|
||||
},
|
||||
MainWindow,
|
||||
)
|
||||
dia.Resize(fyne.NewSize(512, 256))
|
||||
dia.Show()
|
||||
})
|
||||
RoomTab.RemoveBtn = widget.NewButton(i18n.T("gui.room.button.remove"), func() {
|
||||
showDialogIfError(controller.Instance.LiveRooms().DeleteRoom(PlaylistManager.Index))
|
||||
RoomTab.Rooms.Select(0)
|
||||
RoomTab.Rooms.Refresh()
|
||||
})
|
||||
RoomTab.Rooms.OnSelected = func(id widget.ListItemID) {
|
||||
rom := controller.Instance.LiveRooms().Get(PlaylistManager.Index)
|
||||
if rom != nil {
|
||||
rom.EventManager().Unregister("gui.liveroom.status")
|
||||
}
|
||||
RoomTab.Index = id
|
||||
rom = controller.Instance.LiveRooms().Get(RoomTab.Index)
|
||||
rom.EventManager().RegisterA(liveclient.EventStatusChange, "gui.liveroom.status", func(event *event.Event) {
|
||||
d := event.Data.(liveclient.StatusChangeEvent)
|
||||
if d.Connected {
|
||||
RoomTab.Status.SetText(i18n.T("gui.room.status.connected"))
|
||||
} else {
|
||||
RoomTab.Status.SetText(i18n.T("gui.room.status.disconnected"))
|
||||
}
|
||||
RoomTab.Status.Refresh()
|
||||
})
|
||||
RoomTab.RoomTitle.SetText(rom.Title())
|
||||
RoomTab.AutoConnect.SetChecked(rom.Model().AutoConnect)
|
||||
if controller.Instance.LiveRooms().GetRoomStatus(RoomTab.Index) {
|
||||
RoomTab.Status.SetText(i18n.T("gui.room.status.connected"))
|
||||
} else {
|
||||
RoomTab.Status.SetText(i18n.T("gui.room.status.disconnected"))
|
||||
}
|
||||
RoomTab.Status.Refresh()
|
||||
}
|
||||
return container.NewHBox(
|
||||
container.NewBorder(
|
||||
nil, container.NewCenter(container.NewHBox(RoomTab.AddBtn, RoomTab.RemoveBtn)),
|
||||
nil, nil,
|
||||
RoomTab.Rooms,
|
||||
),
|
||||
widget.NewSeparator(),
|
||||
)
|
||||
}
|
||||
|
||||
func createRoomController() fyne.CanvasObject {
|
||||
RoomTab.ConnectBtn = widget.NewButton(i18n.T("gui.room.btn.connect"), func() {
|
||||
RoomTab.ConnectBtn.Disable()
|
||||
go func() {
|
||||
_ = controller.Instance.LiveRooms().Connect(RoomTab.Index)
|
||||
RoomTab.ConnectBtn.Enable()
|
||||
}()
|
||||
})
|
||||
RoomTab.DisConnectBtn = widget.NewButton(i18n.T("gui.room.btn.disconnect"), func() {
|
||||
_ = controller.Instance.LiveRooms().Disconnect(RoomTab.Index)
|
||||
})
|
||||
RoomTab.Status = widget.NewLabel(i18n.T("gui.room.waiting"))
|
||||
RoomTab.RoomTitle = widget.NewLabel("")
|
||||
RoomTab.AutoConnect = widget.NewCheck(i18n.T("gui.room.check.autoconnect"), func(b bool) {
|
||||
rom := controller.Instance.LiveRooms().Get(RoomTab.Index)
|
||||
if rom != nil {
|
||||
rom.Model().AutoConnect = b
|
||||
}
|
||||
return
|
||||
})
|
||||
RoomTab.Rooms.Select(0)
|
||||
return container.NewVBox(
|
||||
RoomTab.RoomTitle,
|
||||
RoomTab.Status,
|
||||
container.NewHBox(widget.NewLabel(i18n.T("gui.room.check.autoconnect")), RoomTab.AutoConnect),
|
||||
container.NewHBox(RoomTab.ConnectBtn, RoomTab.DisConnectBtn),
|
||||
)
|
||||
}
|
||||
@@ -1,19 +1,20 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/common/i18n"
|
||||
"AynaLivePlayer/common/util"
|
||||
"AynaLivePlayer/controller"
|
||||
"AynaLivePlayer/event"
|
||||
"AynaLivePlayer/i18n"
|
||||
"AynaLivePlayer/player"
|
||||
"AynaLivePlayer/util"
|
||||
"AynaLivePlayer/gui/component"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/model"
|
||||
"AynaLivePlayer/resource"
|
||||
"context"
|
||||
"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"
|
||||
)
|
||||
|
||||
type PlayControllerContainer struct {
|
||||
@@ -21,10 +22,11 @@ type PlayControllerContainer struct {
|
||||
Artist *widget.Label
|
||||
Username *widget.Label
|
||||
Cover *canvas.Image
|
||||
coverLoader context.CancelFunc
|
||||
ButtonPrev *widget.Button
|
||||
ButtonSwitch *widget.Button
|
||||
ButtonNext *widget.Button
|
||||
Progress *widget.Slider
|
||||
Progress *component.SliderPlus
|
||||
Volume *widget.Slider
|
||||
ButtonLrc *widget.Button
|
||||
LrcWindowOpen bool
|
||||
@@ -33,62 +35,21 @@ type PlayControllerContainer struct {
|
||||
}
|
||||
|
||||
func (p *PlayControllerContainer) SetDefaultCover() {
|
||||
p.Cover.Resource = nil
|
||||
p.Cover.File = config.GetAssetPath("empty.png")
|
||||
p.Cover.Resource = resource.ImageEmpty
|
||||
p.Cover.Refresh()
|
||||
}
|
||||
|
||||
var PlayController = &PlayControllerContainer{}
|
||||
|
||||
func createPlayController() fyne.CanvasObject {
|
||||
PlayController.Cover = canvas.NewImageFromFile(config.GetAssetPath("empty.png"))
|
||||
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.NewCenter(
|
||||
container.NewHBox(PlayController.ButtonPrev, PlayController.ButtonSwitch, PlayController.ButtonNext))
|
||||
|
||||
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.NewVBox(PlayController.Title, PlayController.Artist, PlayController.Username)
|
||||
|
||||
PlayController.Volume = widget.NewSlider(0, 100)
|
||||
volumeIcon := widget.NewIcon(theme.VolumeMuteIcon())
|
||||
PlayController.ButtonLrc = widget.NewButton(i18n.T("gui.player.button.lrc"), func() {})
|
||||
|
||||
volumeControl := container.NewBorder(nil, nil, container.NewHBox(widget.NewLabel(" "), volumeIcon), nil,
|
||||
container.NewGridWithColumns(3, container.NewMax(PlayController.Volume), PlayController.ButtonLrc))
|
||||
|
||||
registerPlayControllerHandler()
|
||||
|
||||
return container.NewBorder(nil, nil, container.NewHBox(PlayController.Cover, playInfo, widget.NewSeparator()), nil,
|
||||
container.NewVBox(buttonsBox, progressItem, volumeControl))
|
||||
}
|
||||
|
||||
func registerPlayControllerHandler() {
|
||||
PlayController.ButtonPrev.OnTapped = func() {
|
||||
controller.Seek(0, true)
|
||||
controller.Instance.PlayControl().Seek(0, true)
|
||||
}
|
||||
PlayController.ButtonSwitch.OnTapped = func() {
|
||||
if controller.Toggle() {
|
||||
PlayController.ButtonSwitch.Icon = theme.MediaPlayIcon()
|
||||
} else {
|
||||
PlayController.ButtonSwitch.Icon = theme.MediaStopIcon()
|
||||
}
|
||||
controller.Instance.PlayControl().Toggle()
|
||||
}
|
||||
PlayController.ButtonNext.OnTapped = func() {
|
||||
controller.PlayNext()
|
||||
controller.Instance.PlayControl().PlayNext()
|
||||
}
|
||||
|
||||
PlayController.ButtonLrc.OnTapped = func() {
|
||||
@@ -98,118 +59,197 @@ func registerPlayControllerHandler() {
|
||||
}
|
||||
}
|
||||
|
||||
if controller.MainPlayer.ObserveProperty("pause", func(property *mpv.EventProperty) {
|
||||
if property.Data == nil {
|
||||
PlayController.ButtonSwitch.Icon = theme.MediaPlayIcon()
|
||||
return
|
||||
}
|
||||
if property.Data.(mpv.Node).Value.(bool) {
|
||||
PlayController.ButtonSwitch.Icon = theme.MediaPlayIcon()
|
||||
} else {
|
||||
PlayController.ButtonSwitch.Icon = theme.MediaStopIcon()
|
||||
}
|
||||
}) != nil {
|
||||
if controller.Instance.PlayControl().GetPlayer().ObserveProperty(
|
||||
model.PlayerPropPause, "gui.play_controller.pause", func(ev *event.Event) {
|
||||
data := ev.Data.(model.PlayerPropertyUpdateEvent).Value
|
||||
if data == nil {
|
||||
PlayController.ButtonSwitch.Icon = theme.MediaPlayIcon()
|
||||
return
|
||||
}
|
||||
if data.(bool) {
|
||||
PlayController.ButtonSwitch.Icon = theme.MediaPlayIcon()
|
||||
} else {
|
||||
PlayController.ButtonSwitch.Icon = theme.MediaPauseIcon()
|
||||
}
|
||||
}) != nil {
|
||||
l().Error("fail to register handler for switch button with property pause")
|
||||
}
|
||||
|
||||
if controller.MainPlayer.ObserveProperty("percent-pos", func(property *mpv.EventProperty) {
|
||||
if property.Data == nil {
|
||||
PlayController.Progress.Value = 0
|
||||
} else {
|
||||
PlayController.Progress.Value = property.Data.(mpv.Node).Value.(float64) * 10
|
||||
}
|
||||
PlayController.Progress.Refresh()
|
||||
}) != nil {
|
||||
if controller.Instance.PlayControl().GetPlayer().ObserveProperty(
|
||||
model.PlayerPropPercentPos, "gui.play_controller.percent_pos", func(ev *event.Event) {
|
||||
if PlayController.Progress.Dragging {
|
||||
return
|
||||
}
|
||||
data := ev.Data.(model.PlayerPropertyUpdateEvent).Value
|
||||
if data == nil {
|
||||
PlayController.Progress.Value = 0
|
||||
} else {
|
||||
PlayController.Progress.Value = data.(float64) * 10
|
||||
}
|
||||
PlayController.Progress.Refresh()
|
||||
}) != nil {
|
||||
l().Error("fail to register handler for progress bar with property percent-pos")
|
||||
}
|
||||
|
||||
if controller.MainPlayer.ObserveProperty("idle-active", func(property *mpv.EventProperty) {
|
||||
isIdle := property.Data.(mpv.Node).Value.(bool)
|
||||
l().Debug("receive idle active ", isIdle, " set/reset info")
|
||||
// todo: @3
|
||||
if isIdle {
|
||||
PlayController.Progress.Value = 0
|
||||
PlayController.Progress.Max = 0
|
||||
//PlayController.Title.SetText("Title")
|
||||
//PlayController.Artist.SetText("Artist")
|
||||
//PlayController.Username.SetText("Username")
|
||||
//PlayController.SetDefaultCover()
|
||||
} else {
|
||||
PlayController.Progress.Max = 1000
|
||||
}
|
||||
}) != nil {
|
||||
if controller.Instance.PlayControl().GetPlayer().ObserveProperty(
|
||||
model.PlayerPropIdleActive, "gui.play_controller.idle_active", func(ev *event.Event) {
|
||||
isIdle := ev.Data.(model.PlayerPropertyUpdateEvent).Value.(bool)
|
||||
l().Debug("receive idle active ", isIdle, " set/reset info")
|
||||
// todo: @3
|
||||
if isIdle {
|
||||
PlayController.Progress.Value = 0
|
||||
PlayController.Progress.Max = 0
|
||||
//PlayController.Title.SetText("Title")
|
||||
//PlayController.Artist.SetText("Artist")
|
||||
//PlayController.Username.SetText("Username")
|
||||
//PlayController.SetDefaultCover()
|
||||
} else {
|
||||
PlayController.Progress.Max = 1000
|
||||
}
|
||||
}) != nil {
|
||||
l().Error("fail to register handler for progress bar with property idle-active")
|
||||
}
|
||||
|
||||
PlayController.Progress.Max = 0
|
||||
PlayController.Progress.OnChanged = func(f float64) {
|
||||
controller.Seek(f/10, false)
|
||||
PlayController.Progress.OnDragEnd = func(f float64) {
|
||||
controller.Instance.PlayControl().Seek(f/10, false)
|
||||
}
|
||||
|
||||
if controller.MainPlayer.ObserveProperty("time-pos", func(property *mpv.EventProperty) {
|
||||
if property.Data == nil {
|
||||
PlayController.CurrentTime.SetText("0:00")
|
||||
return
|
||||
}
|
||||
PlayController.CurrentTime.SetText(util.FormatTime(int(property.Data.(mpv.Node).Value.(float64))))
|
||||
}) != nil {
|
||||
if controller.Instance.PlayControl().GetPlayer().ObserveProperty(
|
||||
model.PlayerPropTimePos, "gui.play_controller.time_pos", func(ev *event.Event) {
|
||||
data := ev.Data.(model.PlayerPropertyUpdateEvent).Value
|
||||
if data == nil {
|
||||
PlayController.CurrentTime.SetText("0:00")
|
||||
return
|
||||
}
|
||||
PlayController.CurrentTime.SetText(util.FormatTime(int(data.(float64))))
|
||||
}) != nil {
|
||||
l().Error("fail to register handler for current time with property time-pos")
|
||||
}
|
||||
|
||||
if controller.MainPlayer.ObserveProperty("duration", func(property *mpv.EventProperty) {
|
||||
if property.Data == nil {
|
||||
PlayController.TotalTime.SetText("0:00")
|
||||
return
|
||||
}
|
||||
PlayController.TotalTime.SetText(util.FormatTime(int(property.Data.(mpv.Node).Value.(float64))))
|
||||
}) != nil {
|
||||
if controller.Instance.PlayControl().GetPlayer().ObserveProperty(
|
||||
model.PlayerPropDuration, "gui.play_controller.duration", func(ev *event.Event) {
|
||||
data := ev.Data.(model.PlayerPropertyUpdateEvent).Value
|
||||
if data == nil {
|
||||
PlayController.TotalTime.SetText("0:00")
|
||||
return
|
||||
}
|
||||
PlayController.TotalTime.SetText(util.FormatTime(int(data.(float64))))
|
||||
}) != nil {
|
||||
l().Error("fail to register handler for total time with property duration")
|
||||
}
|
||||
|
||||
if controller.MainPlayer.ObserveProperty("volume", func(property *mpv.EventProperty) {
|
||||
l().Trace("receive volume change event", *property)
|
||||
if property.Data == nil {
|
||||
PlayController.Volume.Value = 0
|
||||
} else {
|
||||
PlayController.Volume.Value = property.Data.(mpv.Node).Value.(float64)
|
||||
}
|
||||
if controller.Instance.PlayControl().GetPlayer().ObserveProperty(
|
||||
model.PlayerPropVolume, "gui.play_controller.volume", func(ev *event.Event) {
|
||||
data := ev.Data.(model.PlayerPropertyUpdateEvent).Value
|
||||
if data == nil {
|
||||
PlayController.Volume.Value = 0
|
||||
} else {
|
||||
PlayController.Volume.Value = data.(float64)
|
||||
}
|
||||
|
||||
PlayController.Volume.Refresh()
|
||||
}) != nil {
|
||||
PlayController.Volume.Refresh()
|
||||
}) != nil {
|
||||
l().Error("fail to register handler for progress bar with property percent-pos")
|
||||
}
|
||||
|
||||
PlayController.Volume.OnChanged = func(f float64) {
|
||||
controller.SetVolume(f)
|
||||
controller.Instance.PlayControl().SetVolume(f)
|
||||
}
|
||||
|
||||
controller.MainPlayer.EventHandler.RegisterA(player.EventPlay, "gui.player.updateinfo", func(event *event.Event) {
|
||||
controller.Instance.PlayControl().EventManager().RegisterA(model.EventPlay, "gui.player.updateinfo", func(event *event.Event) {
|
||||
l().Debug("receive EventPlay update player info")
|
||||
media := event.Data.(player.PlayEvent).Media
|
||||
media := event.Data.(model.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)
|
||||
if PlayController.coverLoader != nil {
|
||||
PlayController.coverLoader()
|
||||
}
|
||||
// async update
|
||||
var ctx context.Context
|
||||
ctx, PlayController.coverLoader = context.WithCancel(context.Background())
|
||||
go func() {
|
||||
img := canvas.NewImageFromURI(uri)
|
||||
if img == nil {
|
||||
l().Warn("fail to load parse cover url", media.Cover)
|
||||
PlayController.SetDefaultCover()
|
||||
ch := make(chan *canvas.Image)
|
||||
go func() {
|
||||
picture, err := gutil.NewImageFromPlayerPicture(media.Cover)
|
||||
if err != nil {
|
||||
ch <- nil
|
||||
return
|
||||
}
|
||||
ch <- picture
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case pic := <-ch:
|
||||
if pic == nil {
|
||||
PlayController.SetDefaultCover()
|
||||
return
|
||||
}
|
||||
PlayController.Cover.Resource = pic.Resource
|
||||
PlayController.Cover.Refresh()
|
||||
}
|
||||
PlayController.Cover.Resource = img.Resource
|
||||
PlayController.Cover.Refresh()
|
||||
|
||||
}()
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func createPlayControllerV2() fyne.CanvasObject {
|
||||
PlayController.Cover = canvas.NewImageFromResource(resource.ImageEmpty)
|
||||
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 = component.NewSliderPlus(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.Title.Wrapping = fyne.TextTruncate
|
||||
PlayController.Artist = widget.NewLabel("Artist")
|
||||
PlayController.Username = widget.NewLabel("Username")
|
||||
|
||||
titleUser := component.NewFixedHSplitContainer(
|
||||
PlayController.Title, PlayController.Artist, 0.32)
|
||||
titleUser.SetSepThickness(0)
|
||||
|
||||
playInfo := container.NewBorder(nil, nil, nil, PlayController.Username,
|
||||
titleUser)
|
||||
|
||||
registerPlayControllerHandler()
|
||||
|
||||
return container.NewBorder(nil, nil, container.NewHBox(PlayController.Cover, widget.NewSeparator()), nil,
|
||||
container.NewVBox(playInfo, progressItem, controls))
|
||||
}
|
||||
|
||||
@@ -1,60 +1,89 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/controller"
|
||||
"AynaLivePlayer/event"
|
||||
"AynaLivePlayer/player"
|
||||
"AynaLivePlayer/model"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func createLyricObj(lyric *model.Lyric) []fyne.CanvasObject {
|
||||
lrcs := make([]fyne.CanvasObject, len(lyric.Lyrics))
|
||||
for i := 0; i < len(lrcs); i++ {
|
||||
lr := widget.NewLabelWithStyle(
|
||||
lyric.Lyrics[i].Lyric,
|
||||
fyne.TextAlignCenter, fyne.TextStyle{Italic: true})
|
||||
//lr.Wrapping = fyne.TextWrapWord
|
||||
// todo fix fyne bug
|
||||
lr.Wrapping = fyne.TextWrapBreak
|
||||
lrcs[i] = lr
|
||||
}
|
||||
return lrcs
|
||||
}
|
||||
|
||||
func createLyricWindow() fyne.Window {
|
||||
|
||||
// create widgets
|
||||
w := App.NewWindow("Lyric")
|
||||
currentLrc := newLabelWithWrapping("", fyne.TextWrapBreak)
|
||||
currentLrc.Alignment = fyne.TextAlignCenter
|
||||
lrcs := make([]string, len(controller.CurrentLyric.Lyrics))
|
||||
for i := 0; i < len(lrcs); i++ {
|
||||
lrcs[i] = controller.CurrentLyric.Lyrics[i].Lyric
|
||||
}
|
||||
fullLrc := widget.NewRichTextWithText(strings.Join(lrcs, "\n\n"))
|
||||
fullLrc.Scroll = container.ScrollVerticalOnly
|
||||
fullLrc.Wrapping = fyne.TextWrapWord
|
||||
fullLrc := container.NewVBox(createLyricObj(controller.Instance.PlayControl().GetLyric().Get())...)
|
||||
lrcWindow := container.NewVScroll(fullLrc)
|
||||
prevIndex := 0
|
||||
w.SetContent(container.NewBorder(nil,
|
||||
container.NewVBox(widget.NewSeparator(), currentLrc),
|
||||
nil, nil,
|
||||
fullLrc))
|
||||
lrcWindow))
|
||||
w.Resize(fyne.NewSize(360, 540))
|
||||
w.CenterOnScreen()
|
||||
|
||||
// register handlers
|
||||
controller.CurrentLyric.Handler.RegisterA(player.EventLyricUpdate, "player.lyric.current_lyric", func(event *event.Event) {
|
||||
e := event.Data.(player.LyricUpdateEvent)
|
||||
if e.Lyric == nil {
|
||||
currentLrc.SetText("")
|
||||
return
|
||||
}
|
||||
currentLrc.SetText(e.Lyric.Lyric)
|
||||
})
|
||||
controller.CurrentLyric.Handler.RegisterA(player.EventLyricReload, "player.lyric.new_media", func(event *event.Event) {
|
||||
e := event.Data.(player.LyricReloadEvent)
|
||||
lrcs := make([]string, len(e.Lyrics.Lyrics))
|
||||
for i := 0; i < len(lrcs); i++ {
|
||||
lrcs[i] = e.Lyrics.Lyrics[i].Lyric
|
||||
}
|
||||
fullLrc.Segments[0] = &widget.TextSegment{
|
||||
Style: widget.RichTextStyleInline,
|
||||
Text: strings.Join(lrcs, "\n\n"),
|
||||
}
|
||||
fullLrc.Refresh()
|
||||
})
|
||||
controller.Instance.PlayControl().GetLyric().EventManager().RegisterA(
|
||||
model.EventLyricUpdate, "player.lyric.current_lyric", func(event *event.Event) {
|
||||
e := event.Data.(model.LyricUpdateEvent)
|
||||
if prevIndex >= len(fullLrc.Objects) || e.Lyric.Index >= len(fullLrc.Objects) {
|
||||
// fix race condition
|
||||
return
|
||||
}
|
||||
if e.Lyric == nil {
|
||||
currentLrc.SetText("")
|
||||
return
|
||||
}
|
||||
fullLrc.Objects[prevIndex].(*widget.Label).TextStyle.Bold = false
|
||||
fullLrc.Objects[prevIndex].Refresh()
|
||||
fullLrc.Objects[e.Lyric.Index].(*widget.Label).TextStyle.Bold = true
|
||||
fullLrc.Objects[e.Lyric.Index].Refresh()
|
||||
prevIndex = e.Lyric.Index
|
||||
currentLrc.SetText(e.Lyric.Now.Lyric)
|
||||
lrcWindow.Scrolled(&fyne.ScrollEvent{
|
||||
Scrolled: fyne.Delta{
|
||||
DX: 0,
|
||||
DY: lrcWindow.Offset.Y - float32(e.Lyric.Index-2)/float32(e.Lyric.Total)*lrcWindow.Content.Size().Height,
|
||||
},
|
||||
})
|
||||
fullLrc.Refresh()
|
||||
})
|
||||
controller.Instance.PlayControl().GetLyric().EventManager().RegisterA(
|
||||
model.EventLyricReload, "player.lyric.new_media", func(event *event.Event) {
|
||||
e := event.Data.(model.LyricReloadEvent)
|
||||
lrcs := make([]string, len(e.Lyrics.Lyrics))
|
||||
for i := 0; i < len(lrcs); i++ {
|
||||
lrcs[i] = e.Lyrics.Lyrics[i].Lyric
|
||||
}
|
||||
fullLrc.Objects = createLyricObj(e.Lyrics)
|
||||
//fullLrc.SetText(strings.Join(lrcs, "\n"))
|
||||
//fullLrc.Segments[0] = &widget.TextSegment{
|
||||
// Style: widget.RichTextStyleInline,
|
||||
// Text: strings.Join(lrcs, "\n\n"),
|
||||
//}
|
||||
lrcWindow.Refresh()
|
||||
})
|
||||
|
||||
w.SetOnClosed(func() {
|
||||
controller.CurrentLyric.Handler.Unregister("player.lyric.current_lyric")
|
||||
controller.CurrentLyric.Handler.Unregister("player.lyric.new_media")
|
||||
controller.Instance.PlayControl().GetLyric().EventManager().Unregister("player.lyric.current_lyric")
|
||||
controller.Instance.PlayControl().GetLyric().EventManager().Unregister("player.lyric.new_media")
|
||||
PlayController.LrcWindowOpen = false
|
||||
})
|
||||
return w
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/common/i18n"
|
||||
"AynaLivePlayer/controller"
|
||||
"AynaLivePlayer/event"
|
||||
"AynaLivePlayer/i18n"
|
||||
"AynaLivePlayer/player"
|
||||
"AynaLivePlayer/model"
|
||||
"fmt"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type playlistOperationButton struct {
|
||||
@@ -25,10 +26,10 @@ func (b *playlistOperationButton) Tapped(e *fyne.PointEvent) {
|
||||
func newPlaylistOperationButton() *playlistOperationButton {
|
||||
b := &playlistOperationButton{Index: 0}
|
||||
deleteItem := fyne.NewMenuItem(i18n.T("gui.player.playlist.op.delete"), func() {
|
||||
controller.UserPlaylist.Delete(b.Index)
|
||||
controller.Instance.Playlists().GetCurrent().Delete(b.Index)
|
||||
})
|
||||
topItem := fyne.NewMenuItem(i18n.T("gui.player.playlist.op.top"), func() {
|
||||
controller.UserPlaylist.Move(b.Index, 0)
|
||||
controller.Instance.Playlists().GetCurrent().Move(b.Index, 0)
|
||||
})
|
||||
m := fyne.NewMenu("", deleteItem, topItem)
|
||||
b.menu = m
|
||||
@@ -38,18 +39,16 @@ func newPlaylistOperationButton() *playlistOperationButton {
|
||||
return b
|
||||
}
|
||||
|
||||
type PlaylistContainer struct {
|
||||
Playlist *player.Playlist
|
||||
var UserPlaylist = &struct {
|
||||
Playlist *model.Playlist
|
||||
List *widget.List
|
||||
}
|
||||
|
||||
var UserPlaylist = &PlaylistContainer{}
|
||||
mux sync.RWMutex
|
||||
}{}
|
||||
|
||||
func createPlaylist() fyne.CanvasObject {
|
||||
UserPlaylist.Playlist = controller.UserPlaylist
|
||||
UserPlaylist.Playlist = controller.Instance.Playlists().GetCurrent().Model().Copy()
|
||||
UserPlaylist.List = widget.NewList(
|
||||
func() int {
|
||||
//debug.PrintStack()
|
||||
//todo: @4
|
||||
return UserPlaylist.Playlist.Size()
|
||||
},
|
||||
@@ -62,11 +61,11 @@ func createPlaylist() fyne.CanvasObject {
|
||||
},
|
||||
func(id widget.ListItemID, object fyne.CanvasObject) {
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*widget.Label).SetText(
|
||||
UserPlaylist.Playlist.Playlist[id].Title)
|
||||
UserPlaylist.Playlist.Medias[id].Title)
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(
|
||||
UserPlaylist.Playlist.Playlist[id].Artist)
|
||||
UserPlaylist.Playlist.Medias[id].Artist)
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[2].(*widget.Label).SetText(
|
||||
UserPlaylist.Playlist.Playlist[id].ToUser().Name)
|
||||
UserPlaylist.Playlist.Medias[id].ToUser().Name)
|
||||
object.(*fyne.Container).Objects[1].(*widget.Label).SetText(fmt.Sprintf("%d", id))
|
||||
object.(*fyne.Container).Objects[2].(*playlistOperationButton).Index = id
|
||||
})
|
||||
@@ -85,10 +84,12 @@ func createPlaylist() fyne.CanvasObject {
|
||||
}
|
||||
|
||||
func registerPlaylistHandler() {
|
||||
UserPlaylist.Playlist.Handler.RegisterA(player.EventPlaylistUpdate, "gui.playlist.update", func(event *event.Event) {
|
||||
// @6 Read lock Playlist when updating free after updating.
|
||||
UserPlaylist.Playlist.Lock.RLock()
|
||||
controller.Instance.Playlists().GetCurrent().EventManager().RegisterA(model.EventPlaylistUpdate, "gui.playlist.update", func(event *event.Event) {
|
||||
// Read lock Playlists when updating free after updating.
|
||||
l().Tracef("Playlist update event received: %s", event.Data.(model.PlaylistUpdateEvent).Playlist)
|
||||
UserPlaylist.mux.RLock()
|
||||
UserPlaylist.Playlist = event.Data.(model.PlaylistUpdateEvent).Playlist
|
||||
UserPlaylist.List.Refresh()
|
||||
UserPlaylist.Playlist.Lock.RUnlock()
|
||||
UserPlaylist.mux.RUnlock()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/common/i18n"
|
||||
"AynaLivePlayer/controller"
|
||||
"AynaLivePlayer/i18n"
|
||||
"AynaLivePlayer/gui/component"
|
||||
"fmt"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
@@ -13,40 +13,37 @@ import (
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type PlaylistManagerContainer struct {
|
||||
type PlaylistsTab struct {
|
||||
Playlists *widget.List
|
||||
PlaylistMedia *widget.List
|
||||
Index int
|
||||
AddBtn *widget.Button
|
||||
RemoveBtn *widget.Button
|
||||
SetAsSystemBtn *widget.Button
|
||||
RefreshBtn *widget.Button
|
||||
SetAsSystemBtn *component.AsyncButton
|
||||
RefreshBtn *component.AsyncButton
|
||||
CurrentSystemPlaylist *widget.Label
|
||||
}
|
||||
|
||||
func (p *PlaylistManagerContainer) UpdateCurrentSystemPlaylist() {
|
||||
if config.Player.PlaylistIndex >= len(controller.PlaylistManager) {
|
||||
p.CurrentSystemPlaylist.SetText(i18n.T("gui.playlist.current.none"))
|
||||
}
|
||||
p.CurrentSystemPlaylist.SetText(i18n.T("gui.playlist.current") + controller.PlaylistManager[config.Player.PlaylistIndex].Name)
|
||||
func (p *PlaylistsTab) UpdateCurrentSystemPlaylist() {
|
||||
p.CurrentSystemPlaylist.SetText(i18n.T("gui.playlist.current") + controller.Instance.Playlists().GetDefault().Name())
|
||||
}
|
||||
|
||||
var PlaylistManager = &PlaylistManagerContainer{}
|
||||
var PlaylistManager = &PlaylistsTab{}
|
||||
|
||||
func createPlaylists() fyne.CanvasObject {
|
||||
PlaylistManager.Playlists = widget.NewList(
|
||||
func() int {
|
||||
return len(controller.PlaylistManager)
|
||||
return controller.Instance.Playlists().Size()
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
return widget.NewLabel("AAAAAAAAAAAAAAAA")
|
||||
},
|
||||
func(id widget.ListItemID, object fyne.CanvasObject) {
|
||||
object.(*widget.Label).SetText(
|
||||
controller.PlaylistManager[id].Name)
|
||||
controller.Instance.Playlists().Get(id).Name())
|
||||
})
|
||||
PlaylistManager.AddBtn = widget.NewButton(i18n.T("gui.playlist.button.add"), func() {
|
||||
providerEntry := widget.NewSelect(config.Provider.Priority, nil)
|
||||
providerEntry := widget.NewSelect(controller.Instance.Provider().GetPriority(), nil)
|
||||
idEntry := widget.NewEntry()
|
||||
dia := dialog.NewCustomConfirm(
|
||||
i18n.T("gui.playlist.add.title"),
|
||||
@@ -55,7 +52,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,
|
||||
@@ -64,7 +61,7 @@ func createPlaylists() fyne.CanvasObject {
|
||||
),
|
||||
func(b bool) {
|
||||
if b && len(providerEntry.Selected) > 0 && len(idEntry.Text) > 0 {
|
||||
controller.AddPlaylist(providerEntry.Selected, idEntry.Text)
|
||||
controller.Instance.Playlists().Add(providerEntry.Selected, idEntry.Text)
|
||||
PlaylistManager.Playlists.Refresh()
|
||||
PlaylistManager.PlaylistMedia.Refresh()
|
||||
}
|
||||
@@ -75,7 +72,7 @@ func createPlaylists() fyne.CanvasObject {
|
||||
dia.Show()
|
||||
})
|
||||
PlaylistManager.RemoveBtn = widget.NewButton(i18n.T("gui.playlist.button.remove"), func() {
|
||||
controller.RemovePlaylist(PlaylistManager.Index)
|
||||
controller.Instance.Playlists().Remove(PlaylistManager.Index)
|
||||
//PlaylistManager.Index = 0
|
||||
PlaylistManager.Playlists.Select(0)
|
||||
PlaylistManager.Playlists.Refresh()
|
||||
@@ -83,6 +80,7 @@ func createPlaylists() fyne.CanvasObject {
|
||||
})
|
||||
PlaylistManager.Playlists.OnSelected = func(id widget.ListItemID) {
|
||||
PlaylistManager.Index = id
|
||||
PlaylistManager.PlaylistMedia.Refresh()
|
||||
}
|
||||
return container.NewHBox(
|
||||
container.NewBorder(
|
||||
@@ -95,27 +93,28 @@ func createPlaylists() fyne.CanvasObject {
|
||||
}
|
||||
|
||||
func createPlaylistMedias() fyne.CanvasObject {
|
||||
PlaylistManager.RefreshBtn = createAsyncButton(
|
||||
widget.NewButtonWithIcon(i18n.T("gui.playlist.button.refresh"), theme.ViewRefreshIcon(), nil),
|
||||
PlaylistManager.RefreshBtn = component.NewAsyncButtonWithIcon(
|
||||
i18n.T("gui.playlist.button.refresh"), theme.ViewRefreshIcon(),
|
||||
func() {
|
||||
controller.PreparePlaylistByIndex(PlaylistManager.Index)
|
||||
showDialogIfError(controller.Instance.Playlists().PreparePlaylistByIndex(PlaylistManager.Index))
|
||||
PlaylistManager.PlaylistMedia.Refresh()
|
||||
})
|
||||
PlaylistManager.SetAsSystemBtn = createAsyncButton(
|
||||
widget.NewButton(i18n.T("gui.playlist.button.set_as_system"), nil),
|
||||
PlaylistManager.SetAsSystemBtn = component.NewAsyncButton(
|
||||
i18n.T("gui.playlist.button.set_as_system"),
|
||||
func() {
|
||||
controller.SetSystemPlaylist(PlaylistManager.Index)
|
||||
showDialogIfError(controller.Instance.Playlists().SetDefault(PlaylistManager.Index))
|
||||
PlaylistManager.PlaylistMedia.Refresh()
|
||||
PlaylistManager.UpdateCurrentSystemPlaylist()
|
||||
})
|
||||
|
||||
PlaylistManager.CurrentSystemPlaylist = widget.NewLabel("Current: ")
|
||||
PlaylistManager.UpdateCurrentSystemPlaylist()
|
||||
PlaylistManager.PlaylistMedia = widget.NewList(
|
||||
func() int {
|
||||
if len(controller.PlaylistManager) == 0 {
|
||||
if controller.Instance.Playlists().Size() == 0 {
|
||||
return 0
|
||||
}
|
||||
return controller.PlaylistManager[PlaylistManager.Index].Size()
|
||||
return controller.Instance.Playlists().Get(PlaylistManager.Index).Size()
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
return container.NewBorder(nil, nil,
|
||||
@@ -129,18 +128,19 @@ func createPlaylistMedias() fyne.CanvasObject {
|
||||
newLabelWithWrapping("artist", fyne.TextTruncate)))
|
||||
},
|
||||
func(id widget.ListItemID, object fyne.CanvasObject) {
|
||||
m := controller.PlaylistManager[PlaylistManager.Index].Playlist[id]
|
||||
m := controller.Instance.Playlists().Get(PlaylistManager.Index).Get(id).Copy()
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*widget.Label).SetText(
|
||||
m.Title)
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(
|
||||
m.Artist)
|
||||
object.(*fyne.Container).Objects[1].(*widget.Label).SetText(fmt.Sprintf("%d", id))
|
||||
btns := object.(*fyne.Container).Objects[2].(*fyne.Container).Objects
|
||||
m.User = controller.SystemUser
|
||||
btns[0].(*widget.Button).OnTapped = func() {
|
||||
controller.Play(controller.ToSystemMedia(m))
|
||||
showDialogIfError(controller.Instance.PlayControl().Play(m))
|
||||
}
|
||||
btns[1].(*widget.Button).OnTapped = func() {
|
||||
controller.UserPlaylist.Push(controller.ToSystemMedia(m))
|
||||
controller.Instance.Playlists().GetCurrent().Push(m)
|
||||
}
|
||||
})
|
||||
return container.NewBorder(
|
||||
@@ -1,58 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/controller"
|
||||
"AynaLivePlayer/event"
|
||||
"AynaLivePlayer/i18n"
|
||||
"AynaLivePlayer/liveclient"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type RoomControllerContainer struct {
|
||||
Input *widget.SelectEntry
|
||||
ConnectBtn *widget.Button
|
||||
DisConnectBtn *widget.Button
|
||||
Status *widget.Label
|
||||
}
|
||||
|
||||
var RoomController = &RoomControllerContainer{}
|
||||
|
||||
func createRoomController() fyne.CanvasObject {
|
||||
RoomController.Input = widget.NewSelectEntry(config.LiveRoom.History)
|
||||
RoomController.ConnectBtn = widget.NewButton(i18n.T("gui.room.btn.connect"), func() {
|
||||
RoomController.ConnectBtn.Disable()
|
||||
controller.SetDanmuClient(RoomController.Input.Text)
|
||||
if controller.LiveClient == nil {
|
||||
RoomController.Status.SetText(i18n.T("gui.room.status.failed"))
|
||||
RoomController.ConnectBtn.Enable()
|
||||
RoomController.Status.Refresh()
|
||||
return
|
||||
}
|
||||
RoomController.Input.SetOptions(config.LiveRoom.History)
|
||||
controller.LiveClient.Handler().RegisterA(liveclient.EventStatusChange, "gui.liveclient.status", func(event *event.Event) {
|
||||
d := event.Data.(liveclient.StatusChangeEvent)
|
||||
if d.Connected {
|
||||
RoomController.Status.SetText(i18n.T("gui.room.status.connected"))
|
||||
} else {
|
||||
RoomController.Status.SetText(i18n.T("gui.room.status.disconnected"))
|
||||
}
|
||||
RoomController.Status.Refresh()
|
||||
})
|
||||
go func() {
|
||||
controller.StartDanmuClient()
|
||||
RoomController.ConnectBtn.Enable()
|
||||
}()
|
||||
})
|
||||
RoomController.DisConnectBtn = widget.NewButton(i18n.T("gui.room.btn.disconnect"), func() {
|
||||
controller.ResetDanmuClient()
|
||||
})
|
||||
RoomController.Status = widget.NewLabel(i18n.T("gui.room.waiting"))
|
||||
return container.NewBorder(
|
||||
nil, nil,
|
||||
widget.NewLabel(i18n.T("gui.room.id")), container.NewHBox(RoomController.ConnectBtn, RoomController.DisConnectBtn),
|
||||
container.NewBorder(nil, nil, nil, RoomController.Status, RoomController.Input),
|
||||
)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type RoomLoggerContainer struct {
|
||||
}
|
||||
|
||||
var RoomLogger = &RoomLoggerContainer{}
|
||||
|
||||
func createRoomLogger() fyne.CanvasObject {
|
||||
return widget.NewLabel("广告位招租")
|
||||
}
|
||||
@@ -1,54 +1,52 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/common/i18n"
|
||||
"AynaLivePlayer/controller"
|
||||
"AynaLivePlayer/i18n"
|
||||
"AynaLivePlayer/player"
|
||||
"AynaLivePlayer/gui/component"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type SearchBarContainer struct {
|
||||
Input *widget.Entry
|
||||
Button *widget.Button
|
||||
UseSource *widget.CheckGroup
|
||||
Items []*player.Media
|
||||
}
|
||||
|
||||
var SearchBar = &SearchBarContainer{}
|
||||
var SearchBar = &struct {
|
||||
Input *component.Entry
|
||||
Button *component.AsyncButton
|
||||
UseSource *widget.Select
|
||||
}{}
|
||||
|
||||
func createSearchBar() fyne.CanvasObject {
|
||||
SearchBar.Input = widget.NewEntry()
|
||||
SearchBar.Input = component.NewEntry()
|
||||
SearchBar.Input.SetPlaceHolder(i18n.T("gui.search.placeholder"))
|
||||
SearchBar.Button = widget.NewButton(i18n.T("gui.search.search"), nil)
|
||||
SearchBar.Button.OnTapped = createAsyncOnTapped(SearchBar.Button, func() {
|
||||
keyword := SearchBar.Input.Text
|
||||
s := make([]string, len(SearchBar.UseSource.Selected))
|
||||
|
||||
copy(s, SearchBar.UseSource.Selected)
|
||||
items := make([]*player.Media, 0)
|
||||
for _, p := range s {
|
||||
if r, err := controller.SearchWithProvider(keyword, p); err == nil {
|
||||
items = append(items, r...)
|
||||
}
|
||||
SearchBar.Input.OnKeyUp = func(key *fyne.KeyEvent) {
|
||||
if key.Name == fyne.KeyReturn {
|
||||
SearchBar.Button.OnTapped()
|
||||
}
|
||||
controller.ApplyUser(items, player.SystemUser)
|
||||
}
|
||||
SearchBar.Button = component.NewAsyncButton(i18n.T("gui.search.search"), func() {
|
||||
keyword := SearchBar.Input.Text
|
||||
pr := SearchBar.UseSource.Selected
|
||||
l().Debugf("Search keyword: %s, provider: %s", keyword, pr)
|
||||
items, err := controller.Instance.Provider().SearchWithProvider(keyword, pr)
|
||||
if err != nil {
|
||||
dialog.ShowError(err, MainWindow)
|
||||
}
|
||||
controller.ApplyUser(items, controller.SystemUser)
|
||||
SearchResult.Items = items
|
||||
SearchResult.List.Refresh()
|
||||
})
|
||||
s := make([]string, len(config.Provider.Priority))
|
||||
copy(s, config.Provider.Priority)
|
||||
s := make([]string, len(controller.Instance.Provider().GetPriority()))
|
||||
copy(s, controller.Instance.Provider().GetPriority())
|
||||
|
||||
SearchBar.UseSource = widget.NewCheckGroup(s, nil)
|
||||
SearchBar.UseSource.Horizontal = true
|
||||
SearchBar.UseSource.SetSelected(s)
|
||||
SearchBar.UseSource = widget.NewSelect(s, func(s string) {})
|
||||
if len(s) > 0 {
|
||||
SearchBar.UseSource.SetSelected(s[0])
|
||||
}
|
||||
searchInput := container.NewBorder(
|
||||
nil, nil, widget.NewLabel(i18n.T("gui.search.search")), SearchBar.Button,
|
||||
SearchBar.Input)
|
||||
container.NewBorder(nil, nil, SearchBar.UseSource, nil, SearchBar.Input))
|
||||
return container.NewVBox(
|
||||
searchInput,
|
||||
container.NewHBox(widget.NewLabel(i18n.T("gui.search.filter")), SearchBar.UseSource),
|
||||
widget.NewSeparator())
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/i18n"
|
||||
"AynaLivePlayer/controller"
|
||||
"AynaLivePlayer/i18n"
|
||||
"AynaLivePlayer/player"
|
||||
"AynaLivePlayer/provider"
|
||||
"AynaLivePlayer/model"
|
||||
"fmt"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
@@ -12,13 +11,13 @@ import (
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type SearchResultContainer struct {
|
||||
var SearchResult = &struct {
|
||||
List *widget.List
|
||||
Items []*player.Media
|
||||
Items []*model.Media
|
||||
}{
|
||||
Items: []*model.Media{},
|
||||
}
|
||||
|
||||
var SearchResult = &SearchResultContainer{Items: []*player.Media{}}
|
||||
|
||||
func createSearchList() fyne.CanvasObject {
|
||||
SearchResult.List = widget.NewList(
|
||||
func() int {
|
||||
@@ -42,14 +41,14 @@ func createSearchList() fyne.CanvasObject {
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(
|
||||
SearchResult.Items[id].Artist)
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[2].(*widget.Label).SetText(
|
||||
SearchResult.Items[id].Meta.(provider.Meta).Name)
|
||||
SearchResult.Items[id].Meta.(model.Meta).Name)
|
||||
object.(*fyne.Container).Objects[1].(*widget.Label).SetText(fmt.Sprintf("%d", id))
|
||||
btns := object.(*fyne.Container).Objects[2].(*fyne.Container).Objects
|
||||
btns[0].(*widget.Button).OnTapped = func() {
|
||||
controller.Play(SearchResult.Items[id])
|
||||
showDialogIfError(controller.Instance.PlayControl().Play(SearchResult.Items[id]))
|
||||
}
|
||||
btns[1].(*widget.Button).OnTapped = func() {
|
||||
controller.UserPlaylist.Push(SearchResult.Items[id])
|
||||
controller.Instance.Playlists().GetCurrent().Push(SearchResult.Items[id])
|
||||
}
|
||||
})
|
||||
return container.NewBorder(
|
||||
|
||||
42
gui/theme.go
Normal file
42
gui/theme.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/resource"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
type myTheme struct{}
|
||||
|
||||
var _ fyne.Theme = (*myTheme)(nil)
|
||||
|
||||
// return bundled font resource
|
||||
func (*myTheme) Font(s fyne.TextStyle) fyne.Resource {
|
||||
if s.Monospace {
|
||||
return resource.FontMSYaHei
|
||||
}
|
||||
if s.Bold {
|
||||
if s.Italic {
|
||||
//return theme.DefaultTheme().Font(s)
|
||||
return resource.FontMSYaHeiBold
|
||||
}
|
||||
return resource.FontMSYaHei
|
||||
}
|
||||
if s.Italic {
|
||||
return resource.FontMSYaHei
|
||||
}
|
||||
return resource.FontMSYaHei
|
||||
}
|
||||
|
||||
func (*myTheme) Color(n fyne.ThemeColorName, v fyne.ThemeVariant) color.Color {
|
||||
return theme.DefaultTheme().Color(n, v)
|
||||
}
|
||||
|
||||
func (*myTheme) Icon(n fyne.ThemeIconName) fyne.Resource {
|
||||
return theme.DefaultTheme().Icon(n)
|
||||
}
|
||||
|
||||
func (*myTheme) Size(n fyne.ThemeSizeName) float32 {
|
||||
return theme.DefaultTheme().Size(n)
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package liveclient
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/event"
|
||||
"AynaLivePlayer/logger"
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/common/logger"
|
||||
"errors"
|
||||
"github.com/aynakeya/blivedm"
|
||||
"github.com/sirupsen/logrus"
|
||||
"strconv"
|
||||
@@ -10,18 +11,32 @@ import (
|
||||
)
|
||||
|
||||
type Bilibili struct {
|
||||
client *blivedm.BLiveWsClient
|
||||
handlers *event.Handler
|
||||
client *blivedm.BLiveWsClient
|
||||
eventManager *event.Manager
|
||||
roomName string
|
||||
status bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
LiveClients["bilibili"] = func(id string) (LiveClient, error) {
|
||||
room, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
return nil, errors.New("room id for bilibili should be a integer")
|
||||
}
|
||||
return NewBilibili(room), nil
|
||||
}
|
||||
}
|
||||
|
||||
func NewBilibili(roomId int) LiveClient {
|
||||
cl := &Bilibili{
|
||||
client: &blivedm.BLiveWsClient{ShortId: roomId, Account: blivedm.DanmuAccount{UID: 0}, HearbeatInterval: 10 * time.Second},
|
||||
handlers: event.NewHandler(),
|
||||
client: &blivedm.BLiveWsClient{ShortId: roomId, Account: blivedm.DanmuAccount{UID: 0}, HearbeatInterval: 10 * time.Second},
|
||||
eventManager: event.MainManager.NewChildManager(),
|
||||
roomName: "Unknown",
|
||||
}
|
||||
cl.client.OnDisconnect = func(client *blivedm.BLiveWsClient) {
|
||||
cl.l().Warn("disconnect from websocket connection, maybe try reconnect")
|
||||
cl.Handler().CallA(EventStatusChange, StatusChangeEvent{Connected: false, Client: cl})
|
||||
cl.status = false
|
||||
cl.eventManager.CallA(EventStatusChange, StatusChangeEvent{Connected: false, Client: cl})
|
||||
}
|
||||
cl.client.RegHandler(blivedm.CmdDanmaku, cl.handleMsg)
|
||||
return cl
|
||||
@@ -31,14 +46,27 @@ func (b *Bilibili) ClientName() string {
|
||||
return "bilibili"
|
||||
}
|
||||
|
||||
func (b *Bilibili) Handler() *event.Handler {
|
||||
return b.handlers
|
||||
func (b *Bilibili) RoomName() string {
|
||||
return b.roomName
|
||||
}
|
||||
|
||||
func (b *Bilibili) Status() bool {
|
||||
return b.status
|
||||
}
|
||||
|
||||
func (b *Bilibili) EventManager() *event.Manager {
|
||||
return b.eventManager
|
||||
}
|
||||
|
||||
func (b *Bilibili) Connect() bool {
|
||||
if b.status {
|
||||
return true
|
||||
}
|
||||
b.l().Info("Trying Connect Danmu Server")
|
||||
if b.client.InitRoom() && b.client.ConnectDanmuServer() {
|
||||
b.Handler().CallA(EventStatusChange, StatusChangeEvent{Connected: true, Client: b})
|
||||
b.roomName = b.client.RoomInfo.Title
|
||||
b.status = true
|
||||
b.eventManager.CallA(EventStatusChange, StatusChangeEvent{Connected: true, Client: b})
|
||||
b.l().Info("Connect Success")
|
||||
return true
|
||||
}
|
||||
@@ -48,8 +76,11 @@ func (b *Bilibili) Connect() bool {
|
||||
|
||||
func (b *Bilibili) Disconnect() bool {
|
||||
b.l().Info("Disconnect from danmu server")
|
||||
if b.client == nil {
|
||||
return true
|
||||
}
|
||||
b.client.Disconnect()
|
||||
b.Handler().CallA(EventStatusChange, StatusChangeEvent{Connected: false, Client: b})
|
||||
b.eventManager.CallA(EventStatusChange, StatusChangeEvent{Connected: false, Client: b})
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -82,7 +113,7 @@ func (b *Bilibili) handleMsg(context *blivedm.Context) {
|
||||
}
|
||||
b.l().Debug("receive message", dmsg)
|
||||
go func() {
|
||||
b.handlers.Call(&event.Event{
|
||||
b.eventManager.Call(&event.Event{
|
||||
Id: EventMessageReceive,
|
||||
Cancelled: false,
|
||||
Data: &dmsg,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package liveclient
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/event"
|
||||
"AynaLivePlayer/logger"
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/common/logger"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"testing"
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
|
||||
func TestBilibili_Client(t *testing.T) {
|
||||
logger.Logger.SetLevel(logrus.DebugLevel)
|
||||
lc := NewBilibili(7777)
|
||||
lc := NewBilibili(7777, event.NewManger())
|
||||
//lc := NewBilibili(8524916587)
|
||||
lc.Handler().Register(&event.EventHandler{
|
||||
lc.Handler().Register(&event.Handler{
|
||||
EventId: EventMessageReceive,
|
||||
Name: "test.receivemsg",
|
||||
Handler: func(event *event.Event) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package liveclient
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/event"
|
||||
"AynaLivePlayer/common/event"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package liveclient
|
||||
|
||||
import "AynaLivePlayer/event"
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"errors"
|
||||
)
|
||||
|
||||
const MODULE_NAME = "LiveClient"
|
||||
|
||||
@@ -21,10 +24,31 @@ type DanmuMessage struct {
|
||||
User DanmuUser
|
||||
Message string
|
||||
}
|
||||
|
||||
type LiveClient interface {
|
||||
ClientName() string
|
||||
RoomName() string
|
||||
Connect() bool
|
||||
Disconnect() bool
|
||||
Handler() *event.Handler
|
||||
Status() bool
|
||||
EventManager() *event.Manager
|
||||
}
|
||||
|
||||
type LiveClientCtor func(id string) (LiveClient, error)
|
||||
|
||||
var LiveClients map[string]LiveClientCtor = map[string]LiveClientCtor{}
|
||||
|
||||
func GetAllClientNames() []string {
|
||||
names := make([]string, 0)
|
||||
for key, _ := range LiveClients {
|
||||
names = append(names, key)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func NewLiveClient(clientName, id string) (LiveClient, error) {
|
||||
ctor, ok := LiveClients[clientName]
|
||||
if !ok {
|
||||
return nil, errors.New("no such client")
|
||||
}
|
||||
return ctor(id)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package player
|
||||
package model
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/event"
|
||||
"AynaLivePlayer/common/event"
|
||||
)
|
||||
|
||||
const (
|
||||
EventPlay event.EventId = "player.play"
|
||||
EventPlayed event.EventId = "player.played"
|
||||
EventPlaylistPreInsert event.EventId = "playlist.insert.pre"
|
||||
EventPlaylistInsert event.EventId = "playlist.insert.after"
|
||||
EventPlaylistUpdate event.EventId = "playlist.update"
|
||||
@@ -13,6 +14,10 @@ const (
|
||||
EventLyricReload event.EventId = "lyric.reload"
|
||||
)
|
||||
|
||||
func EventPlayerPropertyUpdate(property PlayerProperty) event.EventId {
|
||||
return event.EventId("player.property.update." + string(property))
|
||||
}
|
||||
|
||||
type PlaylistInsertEvent struct {
|
||||
Playlist *Playlist
|
||||
Index int
|
||||
@@ -20,13 +25,7 @@ type PlaylistInsertEvent struct {
|
||||
}
|
||||
|
||||
type PlaylistUpdateEvent struct {
|
||||
Playlist *Playlist
|
||||
}
|
||||
|
||||
func newPlaylistUpdateEvent(playlist *Playlist) PlaylistUpdateEvent {
|
||||
return PlaylistUpdateEvent{
|
||||
Playlist: playlist,
|
||||
}
|
||||
Playlist *Playlist // Playlist is a copy of the playlist
|
||||
}
|
||||
|
||||
type PlayEvent struct {
|
||||
@@ -36,9 +35,19 @@ type PlayEvent struct {
|
||||
type LyricUpdateEvent struct {
|
||||
Lyrics *Lyric
|
||||
Time float64
|
||||
Lyric *LyricLine
|
||||
Lyric *LyricContext
|
||||
}
|
||||
|
||||
type LyricReloadEvent struct {
|
||||
Lyrics *Lyric
|
||||
}
|
||||
|
||||
type PlayerPropertyUpdateEvent struct {
|
||||
Property PlayerProperty
|
||||
Value PlayerPropertyValue
|
||||
}
|
||||
|
||||
type LiveRoomStatusUpdateEvent struct {
|
||||
RoomTitle string
|
||||
Status bool
|
||||
}
|
||||
17
model/liveroom.go
Normal file
17
model/liveroom.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package model
|
||||
|
||||
import "fmt"
|
||||
|
||||
type LiveRoom struct {
|
||||
ClientName string
|
||||
ID string
|
||||
AutoConnect bool
|
||||
}
|
||||
|
||||
func (r *LiveRoom) String() string {
|
||||
return fmt.Sprintf("<LiveRooms %s:%s>", r.ClientName, r.ID)
|
||||
}
|
||||
|
||||
func (r *LiveRoom) Title() string {
|
||||
return fmt.Sprintf("%s-%s", r.ClientName, r.ID)
|
||||
}
|
||||
117
model/lyric.go
Normal file
117
model/lyric.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/spf13/cast"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var timeTagRegex = regexp.MustCompile("\\[[0-9]+:[0-9]+(\\.[0-9]+)?\\]")
|
||||
|
||||
type LyricLine struct {
|
||||
Time float64 // in seconds
|
||||
Lyric string
|
||||
Translation string
|
||||
}
|
||||
|
||||
type LyricContext struct {
|
||||
Now *LyricLine
|
||||
Index int
|
||||
Total int
|
||||
Prev []*LyricLine
|
||||
Next []*LyricLine
|
||||
}
|
||||
|
||||
type Lyric struct {
|
||||
Lyrics []*LyricLine
|
||||
}
|
||||
|
||||
func LoadLyric(lyric string) *Lyric {
|
||||
tmp := make(map[float64]*LyricLine)
|
||||
times := make([]float64, 0)
|
||||
for _, line := range strings.Split(lyric, "\n") {
|
||||
lrc := timeTagRegex.ReplaceAllString(line, "")
|
||||
if len(lrc) > 0 && lrc[len(lrc)-1] == '\r' {
|
||||
lrc = lrc[:len(lrc)-1]
|
||||
}
|
||||
for _, time := range timeTagRegex.FindAllString(line, -1) {
|
||||
ts := strings.Split(time[1:len(time)-1], ":")
|
||||
t := cast.ToFloat64(ts[0])*60 + cast.ToFloat64(ts[1])
|
||||
times = append(times, t)
|
||||
tmp[t] = &LyricLine{
|
||||
Time: t,
|
||||
Lyric: lrc,
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Float64s(times)
|
||||
lrcs := make([]*LyricLine, len(times))
|
||||
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: "",
|
||||
})
|
||||
return &Lyric{Lyrics: lrcs}
|
||||
}
|
||||
|
||||
func (l *Lyric) findIndexV1(time float64) int {
|
||||
for i := 0; i < len(l.Lyrics)-1; i++ {
|
||||
if l.Lyrics[i].Time <= time && time < l.Lyrics[i+1].Time {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (l *Lyric) findIndex(time float64) int {
|
||||
start := 0
|
||||
end := len(l.Lyrics) - 1
|
||||
mid := (start + end) / 2
|
||||
for start < end {
|
||||
if l.Lyrics[mid].Time <= time && time < l.Lyrics[mid+1].Time {
|
||||
return mid
|
||||
}
|
||||
if l.Lyrics[mid].Time > time {
|
||||
end = mid
|
||||
} else {
|
||||
start = mid
|
||||
}
|
||||
mid = (start + end) / 2
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (l *Lyric) Find(time float64) *LyricLine {
|
||||
idx := l.findIndex(time)
|
||||
if idx == -1 {
|
||||
return nil
|
||||
}
|
||||
return l.Lyrics[idx]
|
||||
}
|
||||
|
||||
func (l *Lyric) FindContext(time float64, prev int, next int) *LyricContext {
|
||||
prev = -prev
|
||||
idx := l.findIndex(time)
|
||||
if idx == -1 {
|
||||
return nil
|
||||
}
|
||||
if (idx + prev) < 0 {
|
||||
prev = -idx
|
||||
}
|
||||
if (idx + 1 + next) > len(l.Lyrics) {
|
||||
next = len(l.Lyrics) - idx - 1
|
||||
}
|
||||
return &LyricContext{
|
||||
Now: l.Lyrics[idx],
|
||||
Index: idx,
|
||||
Total: len(l.Lyrics),
|
||||
Prev: l.Lyrics[idx+prev : idx],
|
||||
Next: l.Lyrics[idx+1 : idx+1+next],
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,31 @@
|
||||
package player
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/magiconair/properties/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testLyric = "[ti:双截棍]\n[ar:周杰伦]\n[al:范特西]\n[00:03.85]双截棍\n[00:07.14]\n[00:30.13]岩烧店的烟味弥漫隔壁是国术馆\n[00:32.57]店里面的妈妈桑茶道有三段\n[00:34.61]教拳脚武术的老板练铁沙掌耍杨家枪\n[00:37.34]硬底子功夫最擅长还会金钟罩铁步衫\n[00:39.67]他们儿子我习惯从小就耳濡目染\n[00:41.96]什么刀枪跟棍棒我都耍的有模有样\n[00:44.22]什么兵器最喜欢双截棍柔中带刚\n[00:46.73]想要去河南嵩山学少林跟武当\n[00:49.24]干什么(客)干什么(客)呼吸吐纳心自在\n[00:51.28]干什么(客)干什么(客)气沉丹田手心开\n[00:53.44]干什么(客)干什么(客)日行千里系沙袋\n[00:56.13]飞檐走壁莫奇怪去去就来\n[00:58.35]一个马步向前一记左钩拳右钩拳\n[01:01.26]一句惹毛我的人有危险一再重演\n[01:04.02]一根我不抽的菸一放好多年它一直在身边\n[01:07.28]干什么(客)干什么(客)我打开任督二脉\n[01:10.27]干什么(客)干什么(客)东亚病夫的招牌\n[01:12.75]干什么(客)干什么(客)已被我一脚踢开\n[02:32.62][01:54.69][01:15.40]快使用双截棍哼哼哈兮\n[02:34.52][01:56.63][01:18.40]快使用双截棍哼哼哈兮\n[02:36.88][01:58.98][01:20.71]习武之人切记仁者无敌\n[02:39.45][02:01.66][01:23.27]是谁在练太极风生水起\n[02:41.97][02:03.93][01:25.74]快使用双截棍哼哼哈兮\n[02:44.42][02:06.11][01:27.75]快使用双截棍哼哼哈兮\n[02:47.01][02:08.54][01:30.13]如果我有轻功飞檐走壁\n[02:49.36][02:11.03][01:32.67]为人耿直不屈一身正气\n[02:53.81]快使用双截棍哼\n[02:56.30]我用手刀防御哼\n[02:58.52]漂亮的回旋踢\n[02:59.52]"
|
||||
|
||||
func TestLyric(t *testing.T) {
|
||||
lryic := NewLyric(testLyric)
|
||||
lryic := LoadLyric(testLyric)
|
||||
for _, lrc := range lryic.Lyrics {
|
||||
fmt.Println(lrc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLyricFind(t *testing.T) {
|
||||
lryic := NewLyric(testLyric)
|
||||
lryic := LoadLyric(testLyric)
|
||||
fmt.Println(lryic.Find(90.4))
|
||||
for _, l := range lryic.FindContext(90.4, -2, 2) {
|
||||
for _, l := range lryic.FindContext(90.4, -2, 2).Next {
|
||||
fmt.Println(l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLyricFindV2(t *testing.T) {
|
||||
lryic := LoadLyric(testLyric)
|
||||
for i := 0.0; i < 170; i += 0.01 {
|
||||
assert.Equal(t, lryic.FindV1(i), lryic.Find(i))
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,23 @@
|
||||
package player
|
||||
package model
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/liveclient"
|
||||
"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
|
||||
@@ -1,4 +1,4 @@
|
||||
package player
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
18
model/player.go
Normal file
18
model/player.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package model
|
||||
|
||||
type AudioDevice struct {
|
||||
Name string
|
||||
Description string
|
||||
}
|
||||
|
||||
type PlayerPropertyValue any
|
||||
type PlayerProperty string
|
||||
|
||||
const (
|
||||
PlayerPropIdleActive PlayerProperty = "idle-active"
|
||||
PlayerPropTimePos PlayerProperty = "time-pos"
|
||||
PlayerPropDuration PlayerProperty = "duration"
|
||||
PlayerPropPercentPos PlayerProperty = "percent-pos"
|
||||
PlayerPropPause PlayerProperty = "pause"
|
||||
PlayerPropVolume PlayerProperty = "volume"
|
||||
)
|
||||
36
model/playlist.go
Normal file
36
model/playlist.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package model
|
||||
|
||||
import "fmt"
|
||||
|
||||
type PlaylistMode int
|
||||
|
||||
const (
|
||||
PlaylistModeNormal PlaylistMode = iota
|
||||
PlaylistModeRandom
|
||||
)
|
||||
|
||||
type Playlist struct {
|
||||
Name string
|
||||
Medias []*Media
|
||||
Mode PlaylistMode
|
||||
Meta Meta
|
||||
}
|
||||
|
||||
func (p Playlist) String() string {
|
||||
return fmt.Sprintf("<Playlist %s>", p.Name)
|
||||
}
|
||||
|
||||
func (p *Playlist) Size() int {
|
||||
return len(p.Medias)
|
||||
}
|
||||
|
||||
func (p *Playlist) Copy() *Playlist {
|
||||
medias := make([]*Media, len(p.Medias))
|
||||
copy(medias, p.Medias)
|
||||
return &Playlist{
|
||||
Name: p.Name,
|
||||
Medias: medias,
|
||||
Mode: p.Mode,
|
||||
Meta: p.Meta,
|
||||
}
|
||||
}
|
||||
6
model/provider.go
Normal file
6
model/provider.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package model
|
||||
|
||||
type Meta struct {
|
||||
Name string
|
||||
Id string
|
||||
}
|
||||
5
model/user.go
Normal file
5
model/user.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package model
|
||||
|
||||
type User struct {
|
||||
Name string
|
||||
}
|
||||
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.
BIN
music/list2/著小生zoki,洛天依 - 【洛天依】影子小姐.mp3
Normal file
BIN
music/list2/著小生zoki,洛天依 - 【洛天依】影子小姐.mp3
Normal file
Binary file not shown.
@@ -1,98 +0,0 @@
|
||||
package player
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/event"
|
||||
"github.com/spf13/cast"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var timeTagRegex = regexp.MustCompile("\\[[0-9]+:[0-9]+(\\.[0-9]+)?\\]")
|
||||
|
||||
type LyricLine struct {
|
||||
Time float64 // in seconds
|
||||
Lyric string
|
||||
}
|
||||
|
||||
type Lyric struct {
|
||||
Lyrics []LyricLine
|
||||
Handler *event.Handler
|
||||
prev float64
|
||||
}
|
||||
|
||||
func (l *Lyric) Reload(lyric string) {
|
||||
tmp := make(map[float64]LyricLine)
|
||||
times := make([]float64, 0)
|
||||
for _, line := range strings.Split(lyric, "\n") {
|
||||
lrc := timeTagRegex.ReplaceAllString(line, "")
|
||||
for _, time := range timeTagRegex.FindAllString(line, -1) {
|
||||
ts := strings.Split(time[1:len(time)-1], ":")
|
||||
t := cast.ToFloat64(ts[0])*60 + cast.ToFloat64(ts[1])
|
||||
times = append(times, t)
|
||||
tmp[t] = LyricLine{
|
||||
Time: t,
|
||||
Lyric: lrc,
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Float64s(times)
|
||||
lrcs := make([]LyricLine, len(times))
|
||||
for index, time := range times {
|
||||
lrcs[index] = tmp[time]
|
||||
}
|
||||
lrcs = append(lrcs, LyricLine{
|
||||
Time: 99999999999,
|
||||
Lyric: "",
|
||||
})
|
||||
l.Lyrics = lrcs
|
||||
l.Handler.CallA(EventLyricReload, LyricReloadEvent{Lyrics: l})
|
||||
return
|
||||
}
|
||||
|
||||
func (l *Lyric) Update(time float64) {
|
||||
lrc := l.Find(time)
|
||||
if lrc == nil {
|
||||
return
|
||||
}
|
||||
if l.prev == lrc.Time {
|
||||
return
|
||||
}
|
||||
l.prev = lrc.Time
|
||||
l.Handler.CallA(EventLyricUpdate, LyricUpdateEvent{
|
||||
Lyrics: l,
|
||||
Time: time,
|
||||
Lyric: lrc,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (l *Lyric) Find(time float64) *LyricLine {
|
||||
for i := 0; i < len(l.Lyrics)-1; i++ {
|
||||
if l.Lyrics[i].Time <= time && time < l.Lyrics[i+1].Time {
|
||||
return &l.Lyrics[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Lyric) FindContext(time float64, prev int, next int) []LyricLine {
|
||||
for i := 0; i < len(l.Lyrics)-1; i++ {
|
||||
if l.Lyrics[i].Time <= time && time < l.Lyrics[i+1].Time {
|
||||
if (i + prev) < 0 {
|
||||
prev = -i
|
||||
}
|
||||
if (i + 1 + next) > len(l.Lyrics) {
|
||||
next = len(l.Lyrics) - i - 1
|
||||
}
|
||||
return l.Lyrics[i+prev : i+1+next]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewLyric(lyric string) *Lyric {
|
||||
l := &Lyric{Handler: event.NewHandler(), prev: -1}
|
||||
l.Reload(lyric)
|
||||
return l
|
||||
}
|
||||
196
player/player.go
196
player/player.go
@@ -1,186 +1,24 @@
|
||||
package player
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/event"
|
||||
"AynaLivePlayer/logger"
|
||||
"AynaLivePlayer/util"
|
||||
"github.com/aynakeya/go-mpv"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/common/logger"
|
||||
"AynaLivePlayer/model"
|
||||
)
|
||||
|
||||
const MODULE_PLAYER = "Player.Player"
|
||||
var lg = logger.Logger.WithField("Module", "PlayControl")
|
||||
|
||||
type PropertyHandlerFunc func(property *mpv.EventProperty)
|
||||
|
||||
type Player struct {
|
||||
running bool
|
||||
libmpv *mpv.Mpv
|
||||
Playing *Media
|
||||
PropertyHandler map[string][]PropertyHandlerFunc
|
||||
EventHandler *event.Handler
|
||||
}
|
||||
|
||||
func NewPlayer() *Player {
|
||||
player := &Player{
|
||||
running: true,
|
||||
libmpv: mpv.Create(),
|
||||
PropertyHandler: make(map[string][]PropertyHandlerFunc),
|
||||
EventHandler: event.NewHandler(),
|
||||
}
|
||||
err := player.libmpv.Initialize()
|
||||
if err != nil {
|
||||
player.l().Error("initialize libmpv failed")
|
||||
return nil
|
||||
}
|
||||
player.libmpv.SetOptionString("vo", "null")
|
||||
player.l().Info("initialize libmpv success")
|
||||
return player
|
||||
}
|
||||
|
||||
func (p *Player) Start() {
|
||||
p.l().Info("starting mpv player")
|
||||
go func() {
|
||||
for p.running {
|
||||
e := p.libmpv.WaitEvent(1)
|
||||
if e == nil {
|
||||
p.l().Warn("event loop got nil event")
|
||||
}
|
||||
p.l().Trace("new event", e)
|
||||
if e.EventId == mpv.EVENT_PROPERTY_CHANGE {
|
||||
property := e.Property()
|
||||
p.l().Trace("receive property change event", property)
|
||||
for _, handler := range p.PropertyHandler[property.Name] {
|
||||
// todo: @3
|
||||
go handler(&property)
|
||||
}
|
||||
}
|
||||
if e.EventId == mpv.EVENT_SHUTDOWN {
|
||||
p.l().Info("libmpv shutdown")
|
||||
p.Stop()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *Player) Stop() {
|
||||
p.l().Info("stopping mpv player")
|
||||
p.running = false
|
||||
p.libmpv.TerminateDestroy()
|
||||
}
|
||||
|
||||
func (p *Player) l() *logrus.Entry {
|
||||
return logger.Logger.WithField("Module", MODULE_PLAYER)
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
err := p.libmpv.SetPropertyString("referrer", val)
|
||||
if err != nil {
|
||||
p.l().Warn("set player referrer failed", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
p.l().Debugf("mpv command load file %s %s", media.Title, media.Url)
|
||||
if err := p.libmpv.Command([]string{"loadfile", media.Url}); err != nil {
|
||||
p.l().Warn("mpv load media failed", media)
|
||||
return err
|
||||
}
|
||||
p.Playing = media
|
||||
p.EventHandler.CallA(EventPlay, PlayEvent{Media: media})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Player) IsPaused() bool {
|
||||
property, err := p.libmpv.GetProperty("pause", mpv.FORMAT_FLAG)
|
||||
if err != nil {
|
||||
p.l().Warn("get property pause failed", err)
|
||||
return false
|
||||
}
|
||||
return property.(bool)
|
||||
}
|
||||
|
||||
func (p *Player) Pause() error {
|
||||
p.l().Tracef("pause")
|
||||
return p.libmpv.SetProperty("pause", mpv.FORMAT_FLAG, true)
|
||||
}
|
||||
|
||||
func (p *Player) Unpause() error {
|
||||
p.l().Tracef("unpause")
|
||||
return p.libmpv.SetProperty("pause", mpv.FORMAT_FLAG, false)
|
||||
}
|
||||
|
||||
// SetVolume set mpv volume, from 0.0 - 100.0
|
||||
func (p *Player) SetVolume(volume float64) error {
|
||||
p.l().Tracef("set volume to %f", volume)
|
||||
return p.libmpv.SetProperty("volume", mpv.FORMAT_DOUBLE, volume)
|
||||
}
|
||||
|
||||
func (p *Player) IsIdle() bool {
|
||||
property, err := p.libmpv.GetProperty("idle-active", mpv.FORMAT_FLAG)
|
||||
if err != nil {
|
||||
p.l().Warn("get property idle-active failed", err)
|
||||
return false
|
||||
}
|
||||
return property.(bool)
|
||||
}
|
||||
|
||||
// Seek change position for current file
|
||||
// absolute = true : position is the time in second
|
||||
// absolute = false: position is in percentage eg 0.1 0.2
|
||||
func (p *Player) Seek(position float64, absolute bool) error {
|
||||
p.l().Tracef("seek to %f (absolute=%t)", position, absolute)
|
||||
if absolute {
|
||||
return p.libmpv.SetProperty("time-pos", mpv.FORMAT_DOUBLE, position)
|
||||
} else {
|
||||
return p.libmpv.SetProperty("percent-pos", mpv.FORMAT_DOUBLE, position)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Player) ObserveProperty(property string, handler ...PropertyHandlerFunc) error {
|
||||
p.l().Trace("add property observer for mpv")
|
||||
p.PropertyHandler[property] = append(p.PropertyHandler[property], handler...)
|
||||
if len(p.PropertyHandler[property]) == 1 {
|
||||
return p.libmpv.ObserveProperty(util.Hash64(property), property, mpv.FORMAT_NODE)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AudioDevice struct {
|
||||
Name string
|
||||
Description string
|
||||
}
|
||||
|
||||
// GetAudioDeviceList get output device for mpv
|
||||
// return format is []AudioDevice
|
||||
func (p *Player) GetAudioDeviceList() ([]AudioDevice, error) {
|
||||
p.l().Trace("getting audio device list for mpv")
|
||||
property, err := p.libmpv.GetProperty("audio-device-list", mpv.FORMAT_STRING)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dl := make([]AudioDevice, 0)
|
||||
gjson.Parse(property.(string)).ForEach(func(key, value gjson.Result) bool {
|
||||
dl = append(dl, AudioDevice{
|
||||
Name: value.Get("name").String(),
|
||||
Description: value.Get("description").String(),
|
||||
})
|
||||
return true
|
||||
})
|
||||
return dl, nil
|
||||
}
|
||||
|
||||
func (p *Player) SetAudioDevice(device string) error {
|
||||
p.l().Tracef("set audio device %s for mpv", device)
|
||||
return p.libmpv.SetPropertyString("audio-device", device)
|
||||
type IPlayer interface {
|
||||
Start()
|
||||
Stop()
|
||||
Play(media *model.Media) error
|
||||
IsPaused() bool
|
||||
Pause() error
|
||||
Unpause() error
|
||||
SetVolume(volume float64) error
|
||||
IsIdle() bool
|
||||
Seek(position float64, absolute bool) error
|
||||
ObserveProperty(property model.PlayerProperty, name string, handler event.HandlerFunc) error
|
||||
GetAudioDeviceList() ([]model.AudioDevice, error)
|
||||
SetAudioDevice(device string) error
|
||||
}
|
||||
|
||||
204
player/player_mpv.go
Normal file
204
player/player_mpv.go
Normal file
@@ -0,0 +1,204 @@
|
||||
package player
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/common/util"
|
||||
"AynaLivePlayer/model"
|
||||
"github.com/aynakeya/go-mpv"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
var mpvPropertyMap = map[model.PlayerProperty]string{
|
||||
model.PlayerPropDuration: "duration",
|
||||
model.PlayerPropTimePos: "time-pos",
|
||||
model.PlayerPropIdleActive: "idle-active",
|
||||
model.PlayerPropPercentPos: "percent-pos",
|
||||
model.PlayerPropPause: "pause",
|
||||
model.PlayerPropVolume: "volume",
|
||||
}
|
||||
|
||||
var mpvPropertyMapInv = map[string]model.PlayerProperty{
|
||||
"duration": model.PlayerPropDuration,
|
||||
"time-pos": model.PlayerPropTimePos,
|
||||
"idle-active": model.PlayerPropIdleActive,
|
||||
"percent-pos": model.PlayerPropPercentPos,
|
||||
"pause": model.PlayerPropPause,
|
||||
"volume": model.PlayerPropVolume,
|
||||
}
|
||||
|
||||
type MpvPlayer struct {
|
||||
running bool
|
||||
libmpv *mpv.Mpv
|
||||
Playing *model.Media
|
||||
propertyWatchedFlag map[model.PlayerProperty]int
|
||||
eventManager *event.Manager
|
||||
}
|
||||
|
||||
func NewMpvPlayer() IPlayer {
|
||||
player := &MpvPlayer{
|
||||
running: true,
|
||||
libmpv: mpv.Create(),
|
||||
propertyWatchedFlag: make(map[model.PlayerProperty]int),
|
||||
eventManager: event.MainManager.NewChildManager(),
|
||||
}
|
||||
err := player.libmpv.Initialize()
|
||||
if err != nil {
|
||||
lg.Error("[MPV PlayControl] initialize libmpv failed")
|
||||
return nil
|
||||
}
|
||||
_ = player.libmpv.SetOptionString("vo", "null")
|
||||
lg.Info("[MPV PlayControl] initialize libmpv success")
|
||||
player.Start()
|
||||
return player
|
||||
}
|
||||
|
||||
func (p *MpvPlayer) Start() {
|
||||
lg.Info("[MPV PlayControl] starting mpv player")
|
||||
go func() {
|
||||
for p.running {
|
||||
e := p.libmpv.WaitEvent(1)
|
||||
if e == nil {
|
||||
lg.Warn("[MPV PlayControl] event loop got nil event")
|
||||
}
|
||||
lg.Trace("[MPV PlayControl] new event", e)
|
||||
if e.EventId == mpv.EVENT_PROPERTY_CHANGE {
|
||||
eventProperty := e.Property()
|
||||
property, ok := mpvPropertyMapInv[eventProperty.Name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
var value interface{} = nil
|
||||
if eventProperty.Data != nil {
|
||||
value = eventProperty.Data.(mpv.Node).Value
|
||||
}
|
||||
p.eventManager.CallA(
|
||||
model.EventPlayerPropertyUpdate(property),
|
||||
model.PlayerPropertyUpdateEvent{
|
||||
Property: property,
|
||||
Value: value,
|
||||
})
|
||||
|
||||
}
|
||||
if e.EventId == mpv.EVENT_SHUTDOWN {
|
||||
lg.Info("[MPV PlayControl] libmpv shutdown")
|
||||
p.Stop()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *MpvPlayer) Stop() {
|
||||
lg.Info("[MPV PlayControl] stopping mpv player")
|
||||
p.running = false
|
||||
p.libmpv.TerminateDestroy()
|
||||
}
|
||||
|
||||
func (p *MpvPlayer) Play(media *model.Media) error {
|
||||
lg.Infof("[MPV PlayControl] Play media %s", media.Url)
|
||||
if val, ok := media.Header["User-Agent"]; ok {
|
||||
lg.Debug("[MPV PlayControl] set user-agent for mpv player")
|
||||
err := p.libmpv.SetPropertyString("user-agent", val)
|
||||
if err != nil {
|
||||
lg.Warn("[MPV PlayControl] set player user-agent failed", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := media.Header["Referer"]; ok {
|
||||
lg.Debug("[MPV PlayControl] set referrer for mpv player")
|
||||
err := p.libmpv.SetPropertyString("referrer", val)
|
||||
if err != nil {
|
||||
lg.Warn("[MPV PlayControl] set player referrer failed", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
lg.Debugf("mpv command load file %s %s", media.Title, media.Url)
|
||||
if err := p.libmpv.Command([]string{"loadfile", media.Url}); err != nil {
|
||||
lg.Warn("[MPV PlayControl] mpv load media failed", media)
|
||||
return err
|
||||
}
|
||||
p.Playing = media
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *MpvPlayer) IsPaused() bool {
|
||||
property, err := p.libmpv.GetProperty("pause", mpv.FORMAT_FLAG)
|
||||
if err != nil {
|
||||
lg.Warn("[MPV PlayControl] get property pause failed", err)
|
||||
return false
|
||||
}
|
||||
return property.(bool)
|
||||
}
|
||||
|
||||
func (p *MpvPlayer) Pause() error {
|
||||
lg.Tracef("[MPV PlayControl] pause")
|
||||
return p.libmpv.SetProperty("pause", mpv.FORMAT_FLAG, true)
|
||||
}
|
||||
|
||||
func (p *MpvPlayer) Unpause() error {
|
||||
lg.Tracef("[MPV PlayControl] unpause")
|
||||
return p.libmpv.SetProperty("pause", mpv.FORMAT_FLAG, false)
|
||||
}
|
||||
|
||||
// SetVolume set mpv volume, from 0.0 - 100.0
|
||||
func (p *MpvPlayer) SetVolume(volume float64) error {
|
||||
lg.Tracef("[MPV PlayControl] set volume to %f", volume)
|
||||
return p.libmpv.SetProperty("volume", mpv.FORMAT_DOUBLE, volume)
|
||||
}
|
||||
|
||||
func (p *MpvPlayer) IsIdle() bool {
|
||||
property, err := p.libmpv.GetProperty("idle-active", mpv.FORMAT_FLAG)
|
||||
if err != nil {
|
||||
lg.Warn("[MPV PlayControl] get property idle-active failed", err)
|
||||
return false
|
||||
}
|
||||
return property.(bool)
|
||||
}
|
||||
|
||||
// Seek change position for current file
|
||||
// absolute = true : position is the time in second
|
||||
// absolute = false: position is in percentage eg 0.1 0.2
|
||||
func (p *MpvPlayer) Seek(position float64, absolute bool) error {
|
||||
lg.Tracef("[MPV PlayControl] seek to %f (absolute=%t)", position, absolute)
|
||||
if absolute {
|
||||
return p.libmpv.SetProperty("time-pos", mpv.FORMAT_DOUBLE, position)
|
||||
} else {
|
||||
return p.libmpv.SetProperty("percent-pos", mpv.FORMAT_DOUBLE, position)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *MpvPlayer) ObserveProperty(property model.PlayerProperty, name string, handler event.HandlerFunc) error {
|
||||
lg.Trace("[MPV PlayControl] add property observer for mpv")
|
||||
p.eventManager.RegisterA(
|
||||
model.EventPlayerPropertyUpdate(property),
|
||||
name, handler)
|
||||
if _, ok := p.propertyWatchedFlag[property]; !ok {
|
||||
p.propertyWatchedFlag[property] = 1
|
||||
return p.libmpv.ObserveProperty(util.Hash64(mpvPropertyMap[property]), mpvPropertyMap[property], mpv.FORMAT_NODE)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAudioDeviceList get output device for mpv
|
||||
// return format is []AudioDevice
|
||||
func (p *MpvPlayer) GetAudioDeviceList() ([]model.AudioDevice, error) {
|
||||
lg.Trace("[MPV PlayControl] getting audio device list for mpv")
|
||||
property, err := p.libmpv.GetProperty("audio-device-list", mpv.FORMAT_STRING)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dl := make([]model.AudioDevice, 0)
|
||||
gjson.Parse(property.(string)).ForEach(func(key, value gjson.Result) bool {
|
||||
dl = append(dl, model.AudioDevice{
|
||||
Name: value.Get("name").String(),
|
||||
Description: value.Get("description").String(),
|
||||
})
|
||||
return true
|
||||
})
|
||||
return dl, nil
|
||||
}
|
||||
|
||||
func (p *MpvPlayer) SetAudioDevice(device string) error {
|
||||
lg.Tracef("[MPV PlayControl] set audio device %s for mpv", device)
|
||||
return p.libmpv.SetPropertyString("audio-device", device)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package player
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/model"
|
||||
"fmt"
|
||||
"github.com/aynakeya/go-mpv"
|
||||
"testing"
|
||||
@@ -18,7 +19,7 @@ func TestPlayer(t *testing.T) {
|
||||
player.ObserveProperty("percent-pos", func(property *mpv.EventProperty) {
|
||||
fmt.Println(2, property.Data)
|
||||
})
|
||||
player.Play(&Media{
|
||||
player.Play(&model.Media{
|
||||
Url: "https://ia600809.us.archive.org/19/items/VillagePeopleYMCAOFFICIALMusicVideo1978/Village%20People%20-%20YMCA%20OFFICIAL%20Music%20Video%201978.mp4",
|
||||
})
|
||||
time.Sleep(time.Second * 15)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user