mirror of
https://github.com/AynaLivePlayer/AynaLivePlayer.git
synced 2025-12-06 18:32:50 +08:00
4
.gitignore
vendored
4
.gitignore
vendored
@@ -6,4 +6,6 @@ music
|
||||
/txtinfo/
|
||||
CMakeCache.txt
|
||||
/config/
|
||||
/release/
|
||||
/release/
|
||||
log.txt
|
||||
go.sum
|
||||
@@ -42,7 +42,9 @@ git submodule set-url pkg/liveroom-sdk https://github.com/AynaLivePlayer/liveroo
|
||||
git submodule update
|
||||
```
|
||||
6. now you can build (please check makefile for more details)
|
||||
```bash
|
||||
```powershell
|
||||
$env:CGO_LDFLAGS="-LC:\Users\vboxuser\Desktop\AynaLivePlayer\libmpv\lib"
|
||||
$env:CGO_CFLAGS="-IC:\Users\vboxuser\Desktop\AynaLivePlayer\libmpv\include"
|
||||
# ... more setup, see makefile
|
||||
go build -o AynaLivePlayer -ldflags -H=windowsgui app/main.go
|
||||
```
|
||||
@@ -37,7 +37,7 @@ var Log = &_LogConfig{
|
||||
|
||||
func setupGlobal() {
|
||||
global.EventManager = event.NewManger(128, 16)
|
||||
global.Logger = loggerRepo.NewZapColoredLogger()
|
||||
global.Logger = loggerRepo.NewZapColoredLogger(Log.Path, !*dev)
|
||||
global.Logger.SetLogLevel(Log.Level)
|
||||
}
|
||||
|
||||
|
||||
10
app/uwu/a.html
Normal file
10
app/uwu/a.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -276,6 +276,10 @@
|
||||
"en": "Show",
|
||||
"zh-CN": "打开"
|
||||
},
|
||||
"gui.update.already_latest_version": {
|
||||
"en": "no update available",
|
||||
"zh-CN": "没有可用更新"
|
||||
},
|
||||
"gui.update.new_version": {
|
||||
"en": "New Version Available",
|
||||
"zh-CN": "有新版本可用"
|
||||
@@ -317,8 +321,8 @@
|
||||
"zh-CN": "点歌冷却"
|
||||
},
|
||||
"plugin.diange.custom_cmd": {
|
||||
"en": "Custom Command (Default one still works)",
|
||||
"zh-CN": "自定义命令 (默认的依然可用)"
|
||||
"en": "Custom Command",
|
||||
"zh-CN": "自定义命令"
|
||||
},
|
||||
"plugin.diange.description": {
|
||||
"en": "Basic Diange Configuration",
|
||||
@@ -484,53 +488,57 @@
|
||||
"en": "Text Output",
|
||||
"zh-CN": "文本输出"
|
||||
},
|
||||
"plugin.webinfo.autostart": {
|
||||
"plugin.wshub.autostart": {
|
||||
"en": "Auto start",
|
||||
"zh-CN": "自动启用"
|
||||
},
|
||||
"plugin.webinfo.description": {
|
||||
"en": "Web output configuration",
|
||||
"zh-CN": "web输出设置"
|
||||
"plugin.wshub.description": {
|
||||
"en": "Websocket Hub Configuration",
|
||||
"zh-CN": "Websocket服务器设置"
|
||||
},
|
||||
"plugin.webinfo.port": {
|
||||
"plugin.wshub.local_host_only": {
|
||||
"en": "only allow local host connection",
|
||||
"zh-CN": "只允许本地连接"
|
||||
},
|
||||
"plugin.wshub.port": {
|
||||
"en": "Port",
|
||||
"zh-CN": "服务器端口"
|
||||
},
|
||||
"plugin.webinfo.server_control": {
|
||||
"plugin.wshub.server_control": {
|
||||
"en": "Control",
|
||||
"zh-CN": "操作"
|
||||
},
|
||||
"plugin.webinfo.server_control.restart": {
|
||||
"plugin.wshub.server_control.restart": {
|
||||
"en": "Restart",
|
||||
"zh-CN": "重启"
|
||||
},
|
||||
"plugin.webinfo.server_control.start": {
|
||||
"plugin.wshub.server_control.start": {
|
||||
"en": "Start",
|
||||
"zh-CN": "启动"
|
||||
},
|
||||
"plugin.webinfo.server_control.stop": {
|
||||
"plugin.wshub.server_control.stop": {
|
||||
"en": "Stop",
|
||||
"zh-CN": "停止"
|
||||
},
|
||||
"plugin.webinfo.server_preview": {
|
||||
"en": "Server Preview",
|
||||
"zh-CN": "效果预览"
|
||||
"plugin.wshub.server_link": {
|
||||
"en": "Websocket Server Link",
|
||||
"zh-CN": "Websocket服务器链接"
|
||||
},
|
||||
"plugin.webinfo.server_status": {
|
||||
"plugin.wshub.server_status": {
|
||||
"en": "Server Status",
|
||||
"zh-CN": "服务器状态"
|
||||
},
|
||||
"plugin.webinfo.server_status.running": {
|
||||
"plugin.wshub.server_status.running": {
|
||||
"en": "Running",
|
||||
"zh-CN": "运行中"
|
||||
},
|
||||
"plugin.webinfo.server_status.stopped": {
|
||||
"plugin.wshub.server_status.stopped": {
|
||||
"en": "Stopped",
|
||||
"zh-CN": "已停止"
|
||||
},
|
||||
"plugin.webinfo.title": {
|
||||
"en": "Web Output",
|
||||
"zh-CN": "Web输出"
|
||||
"plugin.wshub.title": {
|
||||
"en": "Websocket Hub",
|
||||
"zh-CN": "Websocket服务器"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
78
core/events/mapping.go
Normal file
78
core/events/mapping.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var EventsMapping = map[event.EventId]any{
|
||||
LiveRoomAddCmd: LiveRoomAddCmdEvent{},
|
||||
LiveRoomProviderUpdate: LiveRoomProviderUpdateEvent{},
|
||||
LiveRoomRemoveCmd: LiveRoomRemoveCmdEvent{},
|
||||
LiveRoomRoomsUpdate: LiveRoomRoomsUpdateEvent{},
|
||||
LiveRoomStatusUpdate: LiveRoomStatusUpdateEvent{},
|
||||
LiveRoomConfigChangeCmd: LiveRoomConfigChangeCmdEvent{},
|
||||
LiveRoomOperationCmd: LiveRoomOperationCmdEvent{},
|
||||
PlayerVolumeChangeCmd: PlayerVolumeChangeCmdEvent{},
|
||||
PlayerPlayCmd: PlayerPlayCmdEvent{},
|
||||
PlayerPlayErrorUpdate: PlayerPlayErrorUpdateEvent{},
|
||||
PlayerSeekCmd: PlayerSeekCmdEvent{},
|
||||
PlayerToggleCmd: PlayerToggleCmdEvent{},
|
||||
PlayerSetPauseCmd: PlayerSetPauseCmdEvent{},
|
||||
PlayerPlayNextCmd: PlayerPlayNextCmdEvent{},
|
||||
PlayerLyricRequestCmd: PlayerLyricRequestCmdEvent{},
|
||||
PlayerLyricReload: PlayerLyricReloadEvent{},
|
||||
PlayerLyricPosUpdate: PlayerLyricPosUpdateEvent{},
|
||||
PlayerPlayingUpdate: PlayerPlayingUpdateEvent{},
|
||||
PlayerPropertyPauseUpdate: PlayerPropertyPauseUpdateEvent{},
|
||||
PlayerPropertyPercentPosUpdate: PlayerPropertyPercentPosUpdateEvent{},
|
||||
PlayerPropertyIdleActiveUpdate: PlayerPropertyIdleActiveUpdateEvent{},
|
||||
PlayerPropertyTimePosUpdate: PlayerPropertyTimePosUpdateEvent{},
|
||||
PlayerPropertyDurationUpdate: PlayerPropertyDurationUpdateEvent{},
|
||||
PlayerPropertyVolumeUpdate: PlayerPropertyVolumeUpdateEvent{},
|
||||
PlayerVideoPlayerSetWindowHandleCmd: PlayerVideoPlayerSetWindowHandleCmdEvent{},
|
||||
PlayerSetAudioDeviceCmd: PlayerSetAudioDeviceCmdEvent{},
|
||||
PlayerAudioDeviceUpdate: PlayerAudioDeviceUpdateEvent{},
|
||||
PlaylistManagerSetSystemCmd: PlaylistManagerSetSystemCmdEvent{},
|
||||
PlaylistManagerSystemUpdate: PlaylistManagerSystemUpdateEvent{},
|
||||
PlaylistManagerRefreshCurrentCmd: PlaylistManagerRefreshCurrentCmdEvent{},
|
||||
PlaylistManagerGetCurrentCmd: PlaylistManagerGetCurrentCmdEvent{},
|
||||
PlaylistManagerCurrentUpdate: PlaylistManagerCurrentUpdateEvent{},
|
||||
PlaylistManagerInfoUpdate: PlaylistManagerInfoUpdateEvent{},
|
||||
PlaylistManagerAddPlaylistCmd: PlaylistManagerAddPlaylistCmdEvent{},
|
||||
PlaylistManagerRemovePlaylistCmd: PlaylistManagerRemovePlaylistCmdEvent{},
|
||||
MediaProviderUpdate: MediaProviderUpdateEvent{},
|
||||
SearchCmd: SearchCmdEvent{},
|
||||
SearchResultUpdate: SearchResultUpdateEvent{},
|
||||
}
|
||||
|
||||
func init() {
|
||||
for _, v := range []model.PlaylistID{model.PlaylistIDSystem, model.PlaylistIDPlayer, model.PlaylistIDHistory} {
|
||||
EventsMapping[PlaylistDetailUpdate(v)] = PlaylistDetailUpdateEvent{}
|
||||
EventsMapping[PlaylistMoveCmd(v)] = PlaylistMoveCmdEvent{}
|
||||
EventsMapping[PlaylistSetIndexCmd(v)] = PlaylistSetIndexCmdEvent{}
|
||||
EventsMapping[PlaylistDeleteCmd(v)] = PlaylistDeleteCmdEvent{}
|
||||
EventsMapping[PlaylistInsertCmd(v)] = PlaylistInsertCmdEvent{}
|
||||
EventsMapping[PlaylistInsertUpdate(v)] = PlaylistInsertUpdateEvent{}
|
||||
EventsMapping[PlaylistNextCmd(v)] = PlaylistNextCmdEvent{}
|
||||
EventsMapping[PlaylistNextUpdate(v)] = PlaylistNextUpdateEvent{}
|
||||
EventsMapping[PlaylistModeChangeCmd(v)] = PlaylistModeChangeCmdEvent{}
|
||||
EventsMapping[PlaylistModeChangeUpdate(v)] = PlaylistModeChangeUpdateEvent{}
|
||||
}
|
||||
}
|
||||
|
||||
func UnmarshalEventData(eventId event.EventId, data []byte) (any, error) {
|
||||
val, ok := EventsMapping[eventId]
|
||||
if !ok {
|
||||
return nil, errors.New("event id not found")
|
||||
}
|
||||
newVal := reflect.New(reflect.TypeOf(val))
|
||||
err := json.Unmarshal(data, newVal.Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newVal.Elem().Interface(), nil
|
||||
}
|
||||
22
core/events/mapping_test.go
Normal file
22
core/events/mapping_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnmarshalEventData(t *testing.T) {
|
||||
eventData := LiveRoomAddCmdEvent{
|
||||
Title: "test",
|
||||
Provider: "asdfasd",
|
||||
RoomKey: "asdfasdf",
|
||||
}
|
||||
data, err := json.Marshal(eventData)
|
||||
require.NoError(t, err)
|
||||
val, err := UnmarshalEventData(LiveRoomAddCmd, data)
|
||||
require.NoError(t, err)
|
||||
resultData, ok := val.(LiveRoomAddCmdEvent)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, eventData, resultData)
|
||||
}
|
||||
@@ -16,6 +16,12 @@ type PlayerPlayCmdEvent struct {
|
||||
Media model.Media
|
||||
}
|
||||
|
||||
const PlayerPlayErrorUpdate = "update.player.play.error"
|
||||
|
||||
type PlayerPlayErrorUpdateEvent struct {
|
||||
Error error
|
||||
}
|
||||
|
||||
const PlayerSeekCmd = "cmd.player.op.seek"
|
||||
|
||||
type PlayerSeekCmdEvent struct {
|
||||
@@ -31,6 +37,12 @@ const PlayerToggleCmd = "cmd.player.op.toggle"
|
||||
type PlayerToggleCmdEvent struct {
|
||||
}
|
||||
|
||||
const PlayerSetPauseCmd = "cmd.player.op.pause"
|
||||
|
||||
type PlayerSetPauseCmdEvent struct {
|
||||
Pause bool
|
||||
}
|
||||
|
||||
const PlayerPlayNextCmd = "cmd.player.op.next"
|
||||
|
||||
type PlayerPlayNextCmdEvent struct {
|
||||
|
||||
@@ -22,6 +22,14 @@ type PlaylistMoveCmdEvent struct {
|
||||
To int
|
||||
}
|
||||
|
||||
func PlaylistSetIndexCmd(id model.PlaylistID) event.EventId {
|
||||
return event.EventId("cmd.playlist.setindex." + id)
|
||||
}
|
||||
|
||||
type PlaylistSetIndexCmdEvent struct {
|
||||
Index int
|
||||
}
|
||||
|
||||
func PlaylistDeleteCmd(id model.PlaylistID) event.EventId {
|
||||
return event.EventId("cmd.playlist.delete." + id)
|
||||
}
|
||||
|
||||
15
core/events/updater.go
Normal file
15
core/events/updater.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package events
|
||||
|
||||
import "AynaLivePlayer/core/model"
|
||||
|
||||
const CheckUpdateCmd = "cmd.update.check"
|
||||
|
||||
type CheckUpdateCmdEvent struct {
|
||||
}
|
||||
|
||||
const CheckUpdateResultUpdate = "update.update.check"
|
||||
|
||||
type CheckUpdateResultUpdateEvent struct {
|
||||
HasUpdate bool
|
||||
Info model.VersionInfo
|
||||
}
|
||||
4
go.mod
4
go.mod
@@ -33,13 +33,13 @@ require (
|
||||
|
||||
require (
|
||||
fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e // indirect
|
||||
github.com/AynaLivePlayer/blivedm-go v0.0.0-20240408074929-6565ab41764b // indirect
|
||||
github.com/AynaLivePlayer/blivedm-go v0.0.0-20240427041017-949a66917a81 // indirect
|
||||
github.com/PuerkitoBio/goquery v1.7.1 // indirect
|
||||
github.com/XiaoMengXinX/Music163Api-Go v0.1.30 // indirect
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/andybalholm/cascadia v1.2.0 // indirect
|
||||
github.com/aynakeya/deepcolor v1.0.2 // indirect
|
||||
github.com/aynakeya/open-bilibili-live v0.0.5 // indirect
|
||||
github.com/aynakeya/open-bilibili-live v0.0.7 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25 // indirect
|
||||
github.com/fredbi/uri v1.0.0 // indirect
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/component"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"fyne.io/fyne/v2"
|
||||
@@ -105,35 +107,23 @@ func (b *bascicConfig) CreatePanel() fyne.CanvasObject {
|
||||
outputDevice := container.NewBorder(nil, nil,
|
||||
widget.NewLabel(i18n.T("gui.config.basic.audio_device")), nil,
|
||||
deviceSel)
|
||||
//skipWhenErr := container.NewHBox(
|
||||
// widget.NewLabel(i18n.T("gui.config.basic.skip_when_error")),
|
||||
// component.NewCheckOneWayBinding(
|
||||
// i18n.T("gui.config.basic.skip_when_error.prompt"),
|
||||
// &API.PlayControl().Config().AutoNextWhenFail,
|
||||
// API.PlayControl().Config().AutoNextWhenFail),
|
||||
//)
|
||||
//checkUpdateBox := container.NewHBox(
|
||||
// widget.NewLabel(i18n.T("gui.config.basic.auto_check_update")),
|
||||
// component.NewCheckOneWayBinding(
|
||||
// i18n.T("gui.config.basic.auto_check_update.prompt"),
|
||||
// &config.General.AutoCheckUpdate,
|
||||
// config.General.AutoCheckUpdate),
|
||||
//)
|
||||
//checkUpdateBtn := widget.NewButton(i18n.T("gui.config.basic.check_update"), func() {
|
||||
// err := API.App().CheckUpdate()
|
||||
// if err != nil {
|
||||
// showDialogIfError(err)
|
||||
// return
|
||||
// }
|
||||
// if API.App().LatestVersion().Version > API.App().Version().Version {
|
||||
// dialog.ShowCustom(
|
||||
// i18n.T("gui.update.new_version"),
|
||||
// "OK",
|
||||
// widget.NewRichTextFromMarkdown(API.App().LatestVersion().Info),
|
||||
// MainWindow)
|
||||
// }
|
||||
//})
|
||||
//b.panel = container.NewVBox(randomPlaylist, outputDevice, skipPlaylist, skipWhenErr, checkUpdateBox, checkUpdateBtn)
|
||||
b.panel = container.NewVBox(randomPlaylist, outputDevice)
|
||||
skipWhenErr := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("gui.config.basic.skip_when_error")),
|
||||
component.NewCheckOneWayBinding(
|
||||
i18n.T("gui.config.basic.skip_when_error.prompt"),
|
||||
&config.General.PlayNextOnFail,
|
||||
config.General.PlayNextOnFail),
|
||||
)
|
||||
checkUpdateBox := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("gui.config.basic.auto_check_update")),
|
||||
component.NewCheckOneWayBinding(
|
||||
i18n.T("gui.config.basic.auto_check_update.prompt"),
|
||||
&config.General.AutoCheckUpdate,
|
||||
config.General.AutoCheckUpdate),
|
||||
)
|
||||
checkUpdateBtn := widget.NewButton(i18n.T("gui.config.basic.check_update"), func() {
|
||||
global.EventManager.CallA(events.CheckUpdateCmd, events.CheckUpdateCmdEvent{})
|
||||
})
|
||||
b.panel = container.NewVBox(randomPlaylist, skipWhenErr, outputDevice, checkUpdateBox, checkUpdateBtn)
|
||||
return b.panel
|
||||
}
|
||||
|
||||
21
gui/gui.go
21
gui/gui.go
@@ -78,28 +78,9 @@ func Initialize() {
|
||||
dialog.ShowError(err, MainWindow)
|
||||
})
|
||||
|
||||
checkUpdate()
|
||||
MainWindow.SetFixedSize(true)
|
||||
if config.General.ShowSystemTray {
|
||||
setupSysTray()
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//func checkUpdate() {
|
||||
// l().Info("checking updates...")
|
||||
// err := API.App().CheckUpdate()
|
||||
// if err != nil {
|
||||
// showDialogIfError(err)
|
||||
// l().Warnf("check update failed", err)
|
||||
// return
|
||||
// }
|
||||
// l().Infof("latest version: v%s", API.App().LatestVersion().Version)
|
||||
// if API.App().LatestVersion().Version > API.App().Version().Version {
|
||||
// l().Info("new update available")
|
||||
// dialog.ShowCustom(
|
||||
// i18n.T("gui.update.new_version"),
|
||||
// "OK",
|
||||
// widget.NewRichTextFromMarkdown(API.App().LatestVersion().Info),
|
||||
// MainWindow)
|
||||
// }
|
||||
//}
|
||||
|
||||
@@ -44,12 +44,16 @@ func NewImageFromPlayerPicture(picture miaosic.Picture) (*canvas.Image, error) {
|
||||
if uri == nil {
|
||||
return nil, errors.New("fail to fail url")
|
||||
}
|
||||
// NewImageFromURI will return an image with empty resource and file
|
||||
img = canvas.NewImageFromURI(uri)
|
||||
if img == nil {
|
||||
if img == nil || (img.File == "" && img.Resource == nil) {
|
||||
// bug fix, return a new error to indicate fail to read an image
|
||||
return nil, errors.New("fail to read image")
|
||||
}
|
||||
}
|
||||
if img.Resource == nil {
|
||||
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
|
||||
|
||||
@@ -166,7 +166,10 @@ func registerRoomHandlers() {
|
||||
return
|
||||
}
|
||||
RoomTab.rooms[index] = room
|
||||
// add lock to avoid race condition
|
||||
RoomTab.lock.Lock()
|
||||
RoomTab.Rooms.Refresh()
|
||||
RoomTab.lock.Unlock()
|
||||
if index == RoomTab.Index {
|
||||
RoomTab.RoomTitle.SetText(room.DisplayName())
|
||||
RoomTab.RoomID.SetText(room.LiveRoom.Identifier())
|
||||
|
||||
@@ -130,6 +130,8 @@ func registerPlayControllerHandler() {
|
||||
if event.Data.(events.PlayerPlayingUpdateEvent).Removed {
|
||||
PlayController.Progress.Value = 0
|
||||
PlayController.Progress.Max = 0
|
||||
PlayController.TotalTime.SetText("0:00")
|
||||
PlayController.CurrentTime.SetText("0:00")
|
||||
PlayController.Title.SetText("Title")
|
||||
PlayController.Artist.SetText("Artist")
|
||||
PlayController.Username.SetText("Username")
|
||||
@@ -160,6 +162,7 @@ func registerPlayControllerHandler() {
|
||||
picture, err := gutil.NewImageFromPlayerPicture(media.Info.Cover)
|
||||
if err != nil {
|
||||
ch <- nil
|
||||
logger.Errorf("fail to load cover: %v", err)
|
||||
return
|
||||
}
|
||||
ch <- picture
|
||||
|
||||
31
gui/updater.go
Normal file
31
gui/updater.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
func checkUpdate() {
|
||||
global.EventManager.RegisterA(
|
||||
events.CheckUpdateResultUpdate, "gui.updater.check_update", func(event *event.Event) {
|
||||
data := event.Data.(events.CheckUpdateResultUpdateEvent)
|
||||
msg := data.Info.Version.String() + "\n\n\n" + data.Info.Info
|
||||
if data.HasUpdate {
|
||||
dialog.ShowCustom(
|
||||
i18n.T("gui.update.new_version"),
|
||||
"OK",
|
||||
widget.NewRichTextFromMarkdown(msg),
|
||||
MainWindow)
|
||||
} else {
|
||||
dialog.ShowCustom(
|
||||
i18n.T("gui.update.already_latest_version"),
|
||||
"OK",
|
||||
widget.NewRichTextFromMarkdown(""),
|
||||
MainWindow)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/internal/playlist"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
)
|
||||
|
||||
@@ -36,7 +37,17 @@ func handlePlayNext() {
|
||||
|
||||
global.EventManager.RegisterA(
|
||||
events.PlaylistInsertUpdate(model.PlaylistIDPlayer),
|
||||
"internal.controller.playcontrol.playnext_when_insert",
|
||||
"internal.controller.playcontrol.playnext_when_insert.player",
|
||||
func(event *event.Event) {
|
||||
if isIdle {
|
||||
global.EventManager.CallA(events.PlayerPlayNextCmd,
|
||||
events.PlayerPlayNextCmdEvent{})
|
||||
}
|
||||
})
|
||||
|
||||
global.EventManager.RegisterA(
|
||||
events.PlaylistInsertUpdate(model.PlaylistIDSystem),
|
||||
"internal.controller.playcontrol.playnext_when_insert.system",
|
||||
func(event *event.Event) {
|
||||
if isIdle {
|
||||
global.EventManager.CallA(events.PlayerPlayNextCmd,
|
||||
@@ -49,17 +60,27 @@ func handlePlayNext() {
|
||||
"internal.controller.playcontrol.playnext",
|
||||
func(event *event.Event) {
|
||||
if playlist.PlayerPlaylist.Size() > 0 {
|
||||
log.Infof("Try to play next media in player playlist")
|
||||
global.EventManager.CallA(events.PlaylistNextCmd(model.PlaylistIDPlayer),
|
||||
events.PlaylistNextCmdEvent{
|
||||
Remove: true,
|
||||
})
|
||||
} else {
|
||||
log.Infof("Try to play next media in system playlist")
|
||||
global.EventManager.CallA(events.PlaylistNextCmd(model.PlaylistIDSystem),
|
||||
events.PlaylistNextCmdEvent{
|
||||
Remove: true,
|
||||
Remove: false,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
global.EventManager.RegisterA(
|
||||
events.PlayerPlayErrorUpdate,
|
||||
"internal.controller.playcontrol.playnext_on_error",
|
||||
func(event *event.Event) {
|
||||
if isIdle && config.General.PlayNextOnFail {
|
||||
global.EventManager.CallA(events.PlayerPlayNextCmd, events.PlayerPlayNextCmdEvent{})
|
||||
}
|
||||
})
|
||||
|
||||
global.EventManager.RegisterA(events.PlaylistNextUpdate(model.PlaylistIDPlayer),
|
||||
|
||||
@@ -7,11 +7,13 @@ import (
|
||||
"AynaLivePlayer/internal/playlist"
|
||||
"AynaLivePlayer/internal/plugins"
|
||||
"AynaLivePlayer/internal/source"
|
||||
"AynaLivePlayer/internal/updater"
|
||||
"AynaLivePlayer/plugin/diange"
|
||||
"AynaLivePlayer/plugin/durationmgmt"
|
||||
"AynaLivePlayer/plugin/qiege"
|
||||
"AynaLivePlayer/plugin/sourcelogin"
|
||||
"AynaLivePlayer/plugin/textinfo"
|
||||
"AynaLivePlayer/plugin/wshub"
|
||||
)
|
||||
|
||||
func Initialize() {
|
||||
@@ -25,7 +27,9 @@ func Initialize() {
|
||||
diange.NewDiange(), qiege.NewQiege(), sourcelogin.NewSourceLogin(),
|
||||
textinfo.NewTextInfo(),
|
||||
durationmgmt.NewMaxDuration(),
|
||||
wshub.NewWsHub(),
|
||||
)
|
||||
updater.Initialize()
|
||||
}
|
||||
|
||||
func Stop() {
|
||||
|
||||
@@ -20,7 +20,10 @@ func (c *_cfg) OnLoad() {
|
||||
}
|
||||
|
||||
func (c *_cfg) OnSave() {
|
||||
_ = config.SaveJson(c.LiveRoomPath, &c.liveRooms)
|
||||
err := config.SaveJson(c.LiveRoomPath, &c.liveRooms)
|
||||
if err != nil {
|
||||
log.Errorf("fail to save live rooms: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var cfg = &_cfg{
|
||||
|
||||
@@ -50,6 +50,15 @@ func StopAndSave() {
|
||||
func addLiveRoom(roomModel model.LiveRoom) {
|
||||
log.Info("Add live room")
|
||||
room, err := liveroomsdk.CreateLiveRoom(roomModel.LiveRoom)
|
||||
// handle failed to create liveroom
|
||||
if err != nil {
|
||||
log.Errorf("Create live room failed: %s", err)
|
||||
global.EventManager.CallA(
|
||||
events.ErrorUpdate, events.ErrorUpdateEvent{
|
||||
Error: err,
|
||||
})
|
||||
return
|
||||
}
|
||||
if _, ok := liveRooms[room.Config().Identifier()]; ok {
|
||||
log.Errorf("fail to add, room %s already exists", room.Config().Identifier())
|
||||
global.EventManager.CallA(
|
||||
@@ -118,6 +127,7 @@ func registerHandlers() {
|
||||
}
|
||||
_ = room.room.Disconnect()
|
||||
room.room.OnStatusChange(nil)
|
||||
room.room.OnMessage(nil)
|
||||
delete(liveRooms, data.Identifier)
|
||||
log.Infof("success remove live room %s", data.Identifier)
|
||||
sendRoomsUpdateEvent()
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/AynaLivePlayer/miaosic"
|
||||
"github.com/aynakeya/go-mpv"
|
||||
"github.com/tidwall/gjson"
|
||||
"math"
|
||||
)
|
||||
|
||||
var running bool = false
|
||||
@@ -71,6 +72,9 @@ func StopPlayer() {
|
||||
log.Info("mpv player stopped")
|
||||
}
|
||||
|
||||
var prevPercentPos float64 = 0
|
||||
var prevTimePos float64 = 0
|
||||
|
||||
var mpvPropertyHandler = map[string]func(value any){
|
||||
"pause": func(value any) {
|
||||
var data events.PlayerPropertyPauseUpdateEvent
|
||||
@@ -89,6 +93,11 @@ var mpvPropertyHandler = map[string]func(value any){
|
||||
if data.PercentPos < 0.1 {
|
||||
return
|
||||
}
|
||||
// ignore small change
|
||||
if math.Abs(data.PercentPos-prevPercentPos) < 0.5 {
|
||||
return
|
||||
}
|
||||
prevPercentPos = data.PercentPos
|
||||
global.EventManager.CallA(events.PlayerPropertyPercentPosUpdate, data)
|
||||
|
||||
},
|
||||
@@ -120,6 +129,11 @@ var mpvPropertyHandler = map[string]func(value any){
|
||||
if data.TimePos < 0.1 {
|
||||
return
|
||||
}
|
||||
// ignore small change
|
||||
if math.Abs(data.TimePos-prevTimePos) < 0.5 {
|
||||
return
|
||||
}
|
||||
prevTimePos = data.TimePos
|
||||
global.EventManager.CallA(events.PlayerPropertyTimePosUpdate, data)
|
||||
},
|
||||
"duration": func(value any) {
|
||||
@@ -160,6 +174,11 @@ func registerCmdHandler() {
|
||||
mediaUrls, err := miaosic.GetMediaUrl(mediaInfo.Meta, miaosic.QualityAny)
|
||||
if err != nil || len(mediaUrls) == 0 {
|
||||
log.Warn("[MPV PlayControl] get media url failed", err)
|
||||
global.EventManager.CallA(
|
||||
events.PlayerPlayErrorUpdate,
|
||||
events.PlayerPlayErrorUpdateEvent{
|
||||
Error: err,
|
||||
})
|
||||
return
|
||||
}
|
||||
mediaUrl := mediaUrls[0]
|
||||
@@ -180,11 +199,6 @@ func registerCmdHandler() {
|
||||
return
|
||||
}
|
||||
}
|
||||
log.Debugf("mpv command load file %s %s", mediaInfo.Title, mediaUrl.Url)
|
||||
if err := libmpv.Command([]string{"loadfile", mediaUrl.Url}); err != nil {
|
||||
log.Warn("[MPV PlayControl] mpv load media failed", mediaInfo)
|
||||
return
|
||||
}
|
||||
media := evnt.Data.(events.PlayerPlayCmdEvent).Media
|
||||
if m, err := miaosic.GetMediaInfo(media.Info.Meta); err == nil {
|
||||
media.Info = m
|
||||
@@ -193,6 +207,22 @@ func registerCmdHandler() {
|
||||
Media: media,
|
||||
Removed: false,
|
||||
})
|
||||
log.Debugf("mpv command load file %s %s", mediaInfo.Title, mediaUrl.Url)
|
||||
if err := libmpv.Command([]string{"loadfile", mediaUrl.Url}); err != nil {
|
||||
log.Error("[MPV PlayControl] mpv load media failed", mediaInfo)
|
||||
global.EventManager.CallA(
|
||||
events.PlayerPlayErrorUpdate,
|
||||
events.PlayerPlayErrorUpdateEvent{
|
||||
Error: err,
|
||||
})
|
||||
return
|
||||
}
|
||||
global.EventManager.CallA(events.PlayerPropertyTimePosUpdate, events.PlayerPropertyTimePosUpdateEvent{
|
||||
TimePos: 0,
|
||||
})
|
||||
global.EventManager.CallA(events.PlayerPropertyPercentPosUpdate, events.PlayerPropertyPercentPosUpdateEvent{
|
||||
PercentPos: 0,
|
||||
})
|
||||
})
|
||||
global.EventManager.RegisterA(events.PlayerToggleCmd, "player.toggle", func(evnt *event.Event) {
|
||||
property, err := libmpv.GetProperty("pause", mpv.FORMAT_FLAG)
|
||||
@@ -205,6 +235,13 @@ func registerCmdHandler() {
|
||||
log.Warn("[MPV PlayControl] toggle pause failed", err)
|
||||
}
|
||||
})
|
||||
global.EventManager.RegisterA(events.PlayerSetPauseCmd, "player.set_paused", func(evnt *event.Event) {
|
||||
data := evnt.Data.(events.PlayerSetPauseCmdEvent)
|
||||
err := libmpv.SetProperty("pause", mpv.FORMAT_FLAG, data.Pause)
|
||||
if err != nil {
|
||||
log.Warn("[MPV PlayControl] set pause failed", err)
|
||||
}
|
||||
})
|
||||
global.EventManager.RegisterA(events.PlayerSeekCmd, "player.seek", func(evnt *event.Event) {
|
||||
data := evnt.Data.(events.PlayerSeekCmdEvent)
|
||||
log.Debugf("seek to %f (absolute=%t)", data.Position, data.Absolute)
|
||||
|
||||
@@ -37,15 +37,26 @@ func newPlaylist(id model.PlaylistID) *playlist {
|
||||
pl.Delete(e.Index)
|
||||
})
|
||||
global.EventManager.RegisterA(events.PlaylistNextCmd(id), "internal.playlist.next", func(event *event.Event) {
|
||||
log.Infof("Playlist %s recieve next", id)
|
||||
pl.Next(event.Data.(events.PlaylistNextCmdEvent).Remove)
|
||||
})
|
||||
global.EventManager.RegisterA(events.PlaylistModeChangeCmd(id), "internal.playlist.mode", func(event *event.Event) {
|
||||
if pl.mode == model.PlaylistModeRandom {
|
||||
pl.Index = 0
|
||||
}
|
||||
pl.mode = event.Data.(events.PlaylistModeChangeCmdEvent).Mode
|
||||
log.Infof("Playlist %s mode changed to %d", id, pl.mode)
|
||||
global.EventManager.CallA(events.PlaylistModeChangeUpdate(id), events.PlaylistModeChangeUpdateEvent{
|
||||
Mode: pl.mode,
|
||||
})
|
||||
})
|
||||
global.EventManager.RegisterA(events.PlaylistSetIndexCmd(id), "internal.playlist.setindex", func(event *event.Event) {
|
||||
index := event.Data.(events.PlaylistSetIndexCmdEvent).Index
|
||||
if index >= pl.Size() || index < 0 {
|
||||
index = 0
|
||||
}
|
||||
pl.Index = index
|
||||
})
|
||||
return pl
|
||||
}
|
||||
|
||||
@@ -137,13 +148,19 @@ func (p *playlist) Move(src int, dst int) {
|
||||
}
|
||||
|
||||
func (p *playlist) Next(delete bool) {
|
||||
p.Lock.Lock()
|
||||
if p.Size() == 0 {
|
||||
// no media in the playlist
|
||||
// do not issue any event
|
||||
p.Lock.Unlock()
|
||||
return
|
||||
}
|
||||
var index int
|
||||
index = p.Index
|
||||
// add guard
|
||||
if index >= p.Size() {
|
||||
index = 0
|
||||
}
|
||||
if p.mode == model.PlaylistModeRandom {
|
||||
p.Index = rand.Intn(p.Size())
|
||||
} else if p.mode == model.PlaylistModeNormal {
|
||||
@@ -152,17 +169,26 @@ func (p *playlist) Next(delete bool) {
|
||||
p.Index = index
|
||||
}
|
||||
m := p.Medias[index]
|
||||
global.EventManager.CallA(events.PlaylistNextUpdate(p.playlistId), events.PlaylistNextUpdateEvent{
|
||||
Media: m,
|
||||
})
|
||||
// fix race condition
|
||||
currentSize := p.Size() - 1
|
||||
if delete {
|
||||
p.Delete(index)
|
||||
if p.mode == model.PlaylistModeRandom {
|
||||
p.Index = rand.Intn(p.Size())
|
||||
if currentSize == 0 {
|
||||
p.Index = 0
|
||||
} else {
|
||||
p.Index = rand.Intn(currentSize)
|
||||
}
|
||||
} else if p.mode == model.PlaylistModeNormal {
|
||||
p.Index = index
|
||||
} else {
|
||||
p.Index = index
|
||||
}
|
||||
}
|
||||
p.Lock.Unlock()
|
||||
global.EventManager.CallA(events.PlaylistNextUpdate(p.playlistId), events.PlaylistNextUpdateEvent{
|
||||
Media: m,
|
||||
})
|
||||
if delete {
|
||||
p.Delete(index)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
package todo
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
type AppBilibiliChannel struct {
|
||||
latestVersion model.Version
|
||||
}
|
||||
|
||||
func (app *AppBilibiliChannel) Version() model.VersionInfo {
|
||||
return model.VersionInfo{
|
||||
model.Version(config.Version), "",
|
||||
}
|
||||
}
|
||||
|
||||
func (app *AppBilibiliChannel) LatestVersion() model.VersionInfo {
|
||||
return model.VersionInfo{
|
||||
app.latestVersion,
|
||||
fmt.Sprintf("v%s\n\n[https://play-live.bilibili.com/details/1661006726438](https://play-live.bilibili.com/details/1661006726438)", app.latestVersion),
|
||||
}
|
||||
}
|
||||
|
||||
func (app *AppBilibiliChannel) CheckUpdate() error {
|
||||
uri := "https://api.live.bilibili.com/xlive/virtual-interface/v1/app/detail?app_id=1661006726438"
|
||||
resp, err := resty.New().R().Get(uri)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lv := model.VersionFromString(gjson.ParseBytes(resp.Body()).Get("data.version").String())
|
||||
if lv == 0 {
|
||||
return errors.New("failed to get latest version")
|
||||
}
|
||||
app.latestVersion = lv
|
||||
return nil
|
||||
}
|
||||
63
internal/updater/application.go
Normal file
63
internal/updater/application.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/logger"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/tidwall/gjson"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var log logger.ILogger = nil
|
||||
|
||||
func Initialize() {
|
||||
log = global.Logger.WithPrefix("internal.updater")
|
||||
if config.General.AutoCheckUpdate {
|
||||
go func() {
|
||||
info, hasUpdate := CheckUpdate()
|
||||
if !hasUpdate {
|
||||
return
|
||||
}
|
||||
global.EventManager.CallA(
|
||||
events.CheckUpdateResultUpdate,
|
||||
events.CheckUpdateResultUpdateEvent{
|
||||
HasUpdate: hasUpdate,
|
||||
Info: info,
|
||||
})
|
||||
}()
|
||||
}
|
||||
global.EventManager.RegisterA(
|
||||
events.CheckUpdateCmd, "internal.updater.handle",
|
||||
func(evt *event.Event) {
|
||||
info, hasUpdate := CheckUpdate()
|
||||
global.EventManager.CallA(
|
||||
events.CheckUpdateResultUpdate,
|
||||
events.CheckUpdateResultUpdateEvent{
|
||||
HasUpdate: hasUpdate,
|
||||
Info: info,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func CheckUpdate() (model.VersionInfo, bool) {
|
||||
uri := config.General.InfoApiServer + "/api/version/check_update"
|
||||
resp, err := resty.New().R().SetQueryParam("client_version", strconv.Itoa(int(config.Version))).Get(uri)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check update: %s", err.Error())
|
||||
return model.VersionInfo{}, false
|
||||
}
|
||||
result := gjson.ParseBytes(resp.Body())
|
||||
if !result.Get("data.has_update").Bool() {
|
||||
log.Infof("no update available")
|
||||
return model.VersionInfo{}, false
|
||||
}
|
||||
log.Infof("new version available: %s", model.Version(result.Get("data.latest.version").Uint()).String())
|
||||
return model.VersionInfo{
|
||||
Version: model.Version(result.Get("data.latest.version").Uint()),
|
||||
Info: result.Get("data.latest.note").String(),
|
||||
}, true
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
const (
|
||||
ProgramName = "卡西米尔唱片机"
|
||||
Version uint32 = 0x010002
|
||||
Version uint32 = 0x010006
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -5,8 +5,10 @@ type _GeneralConfig struct {
|
||||
Width float32
|
||||
Height float32
|
||||
Language string
|
||||
InfoApiServer string
|
||||
AutoCheckUpdate bool
|
||||
ShowSystemTray bool
|
||||
PlayNextOnFail bool
|
||||
}
|
||||
|
||||
func (c *_GeneralConfig) Name() string {
|
||||
@@ -16,7 +18,9 @@ func (c *_GeneralConfig) Name() string {
|
||||
var General = &_GeneralConfig{
|
||||
Language: "zh-CN",
|
||||
ShowSystemTray: false,
|
||||
InfoApiServer: "http://localhost:9090",
|
||||
AutoCheckUpdate: true,
|
||||
Width: 960,
|
||||
Height: 480,
|
||||
PlayNextOnFail: false,
|
||||
}
|
||||
|
||||
Submodule pkg/liveroom-sdk updated: c7bccf421c...dc1612e5c8
@@ -15,6 +15,51 @@ type LogrusLogger struct {
|
||||
module string
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) DebugW(message string, keysAndValues ...interface{}) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) DebugS(message string, fields logger.LogField) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) InfoW(message string, keysAndValues ...interface{}) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) InfoS(message string, fields logger.LogField) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) WarnW(message string, keysAndValues ...interface{}) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) WarnS(message string, fields logger.LogField) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) ErrorW(message string, keysAndValues ...interface{}) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) ErrorS(message string, fields logger.LogField) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) WithPrefix(prefix string) logger.ILogger {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) SetLogLevel(level logger.LogLevel) {
|
||||
switch level {
|
||||
case logger.LogLevelDebug:
|
||||
|
||||
@@ -3,6 +3,7 @@ package repository
|
||||
import (
|
||||
"AynaLivePlayer/pkg/logger"
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/virtuald/go-paniclog"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"time"
|
||||
@@ -98,7 +99,8 @@ func NewZapLogger() logger.ILogger {
|
||||
return &zapLoggerImpl{SugaredLogger: sugar}
|
||||
}
|
||||
|
||||
func NewZapColoredLogger() logger.ILogger {
|
||||
func NewZapColoredLogger(outPath string, redirectPanic bool) logger.ILogger {
|
||||
f, err := getLogOut(outPath, 5)
|
||||
cfg := zap.NewProductionEncoderConfig()
|
||||
level := zap.NewAtomicLevel()
|
||||
level.SetLevel(zapcore.DebugLevel)
|
||||
@@ -106,11 +108,30 @@ func NewZapColoredLogger() logger.ILogger {
|
||||
cfg.EncodeTime = syslogTimeEncoder
|
||||
cfg.EncodeName = customNamedEncoder
|
||||
cfg.ConsoleSeparator = " "
|
||||
zapLog := zap.New(zapcore.NewCore(
|
||||
zapcore.NewConsoleEncoder(cfg),
|
||||
zapcore.AddSync(colorable.NewColorableStdout()),
|
||||
level,
|
||||
))
|
||||
var zapLog *zap.Logger
|
||||
if err == nil {
|
||||
zapLog = zap.New(
|
||||
zapcore.NewTee(zapcore.NewCore(
|
||||
zapcore.NewConsoleEncoder(cfg),
|
||||
zapcore.AddSync(colorable.NewColorableStdout()),
|
||||
level),
|
||||
zapcore.NewCore(
|
||||
zapcore.NewConsoleEncoder(cfg),
|
||||
zapcore.AddSync(f),
|
||||
level),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
zapLog = zap.New(
|
||||
zapcore.NewCore(
|
||||
zapcore.NewConsoleEncoder(cfg),
|
||||
zapcore.AddSync(colorable.NewColorableStdout()),
|
||||
level),
|
||||
)
|
||||
}
|
||||
if redirectPanic {
|
||||
_, _ = paniclog.RedirectStderr(f)
|
||||
}
|
||||
sugar := zapLog.Sugar()
|
||||
return &zapLoggerImpl{SugaredLogger: sugar, level: level}
|
||||
}
|
||||
|
||||
Submodule pkg/miaosic updated: 95343bb3f9...4b54a301c9
@@ -86,7 +86,7 @@ func NewDiange() *Diange {
|
||||
},
|
||||
},
|
||||
cooldowns: make(map[string]int),
|
||||
log: global.Logger.WithPrefix("Plugin.Logger"),
|
||||
log: global.Logger.WithPrefix("Plugin.Diange"),
|
||||
}
|
||||
return diange
|
||||
}
|
||||
@@ -215,7 +215,14 @@ func (d *Diange) handleMessage(event *event.Event) {
|
||||
|
||||
// match media first
|
||||
|
||||
mediaMeta, found := miaosic.MatchMedia(keywords)
|
||||
var mediaMeta miaosic.MetaData
|
||||
found := false
|
||||
for _, source := range sources {
|
||||
mediaMeta, found = miaosic.MatchMediaByProvider(source, keywords)
|
||||
if found {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var media miaosic.MediaInfo
|
||||
|
||||
@@ -225,22 +232,12 @@ func (d *Diange) handleMessage(event *event.Event) {
|
||||
if len(medias) == 0 || err != nil {
|
||||
continue
|
||||
}
|
||||
// double check blacklist
|
||||
for _, item := range d.blacklist {
|
||||
if item.Exact && item.Value == medias[0].Title {
|
||||
d.log.Warnf("User %s(%s) diange %s is in blacklist %s, ignore", message.User.Username, message.User.Uid, keywords, item.Value)
|
||||
return
|
||||
}
|
||||
if !item.Exact && strings.Contains(medias[0].Title, item.Value) {
|
||||
d.log.Warnf("User %s(%s) diange %s is in blacklist %s, ignore", message.User.Username, message.User.Uid, keywords, item.Value)
|
||||
return
|
||||
}
|
||||
}
|
||||
media = medias[0]
|
||||
found = true
|
||||
break
|
||||
}
|
||||
} else {
|
||||
d.log.Info("Match media: ", mediaMeta)
|
||||
m, err := miaosic.GetMediaInfo(mediaMeta)
|
||||
if err != nil {
|
||||
d.log.Error("Get media info failed: ", err)
|
||||
@@ -250,6 +247,17 @@ func (d *Diange) handleMessage(event *event.Event) {
|
||||
}
|
||||
|
||||
if found {
|
||||
// double check blacklist
|
||||
for _, item := range d.blacklist {
|
||||
if item.Exact && item.Value == media.Title {
|
||||
d.log.Warnf("User %s(%s) diange %s is in blacklist %s, ignore", message.User.Username, message.User.Uid, keywords, item.Value)
|
||||
return
|
||||
}
|
||||
if !item.Exact && strings.Contains(media.Title, item.Value) {
|
||||
d.log.Warnf("User %s(%s) diange %s is in blacklist %s, ignore", message.User.Username, message.User.Uid, keywords, item.Value)
|
||||
return
|
||||
}
|
||||
}
|
||||
if d.SkipSystemPlaylist && d.isCurrentSystem {
|
||||
global.EventManager.CallA(
|
||||
events.PlayerPlayCmd,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package webinfo
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/adapter"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
16
plugin/wshub/events.go
Normal file
16
plugin/wshub/events.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package wshub
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type EventData struct {
|
||||
EventID event.EventId
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
type EventDataReceived struct {
|
||||
EventID event.EventId
|
||||
Data json.RawMessage
|
||||
}
|
||||
176
plugin/wshub/server.go
Normal file
176
plugin/wshub/server.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package wshub
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/pkg/logger"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gorilla/websocket"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
type wsClient struct {
|
||||
conn *websocket.Conn
|
||||
Data chan []byte
|
||||
Close chan byte
|
||||
}
|
||||
|
||||
func (c *wsClient) start() {
|
||||
for {
|
||||
msgType, val, err := c.conn.ReadMessage()
|
||||
if err != nil {
|
||||
c.Close <- 1
|
||||
return
|
||||
}
|
||||
if msgType != websocket.TextMessage {
|
||||
return
|
||||
}
|
||||
var data EventDataReceived
|
||||
err = json.Unmarshal(val, &data)
|
||||
if err != nil {
|
||||
global.Logger.Warn("unmarshal event data failed", err)
|
||||
return
|
||||
}
|
||||
actualEventData, err := events.UnmarshalEventData(data.EventID, data.Data)
|
||||
if err != nil {
|
||||
global.Logger.Warn("unmarshal event data failed", err)
|
||||
return
|
||||
}
|
||||
global.EventManager.CallA(data.EventID, actualEventData)
|
||||
}
|
||||
}
|
||||
|
||||
type wsServer struct {
|
||||
Running bool
|
||||
Server *http.Server
|
||||
clients map[*wsClient]bool
|
||||
mux *http.ServeMux
|
||||
lock sync.RWMutex
|
||||
port *int
|
||||
localhostOnly *bool
|
||||
log logger.ILogger
|
||||
}
|
||||
|
||||
func newWsServer(port *int, localhostOnly *bool) *wsServer {
|
||||
mux := http.NewServeMux()
|
||||
s := &wsServer{
|
||||
Running: false,
|
||||
clients: make(map[*wsClient]bool),
|
||||
mux: mux,
|
||||
port: port,
|
||||
localhostOnly: localhostOnly,
|
||||
log: global.Logger.WithPrefix("plugin.wshub.server"),
|
||||
}
|
||||
mux.HandleFunc("/wsinfo", s.handleWsInfo)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *wsServer) broadcast(data []byte) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
for client := range s.clients {
|
||||
client.Data <- data
|
||||
}
|
||||
}
|
||||
|
||||
func (s *wsServer) register(client *wsClient) {
|
||||
s.lock.Lock()
|
||||
s.clients[client] = true
|
||||
s.lock.Unlock()
|
||||
}
|
||||
|
||||
func (s *wsServer) unregister(client *wsClient) {
|
||||
s.lock.Lock()
|
||||
delete(s.clients, client)
|
||||
s.lock.Unlock()
|
||||
}
|
||||
|
||||
func (s *wsServer) handleWsInfo(w http.ResponseWriter, r *http.Request) {
|
||||
s.log.Debug("connection start")
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
s.log.Warnf("upgrade error: %s", err)
|
||||
return
|
||||
}
|
||||
client := &wsClient{
|
||||
conn: conn,
|
||||
Data: make(chan []byte, 16),
|
||||
Close: make(chan byte, 1),
|
||||
}
|
||||
s.register(client)
|
||||
defer s.unregister(client)
|
||||
go client.start()
|
||||
for {
|
||||
select {
|
||||
case data := <-client.Data:
|
||||
err := client.conn.WriteMessage(websocket.TextMessage, data)
|
||||
if err != nil {
|
||||
s.log.Warn("write message failed", err)
|
||||
return
|
||||
}
|
||||
case _ = <-client.Close:
|
||||
s.log.Infof("client %s close", client.conn.RemoteAddr().String())
|
||||
if err := client.conn.Close(); err != nil {
|
||||
s.log.Warnf("close connection encouter an error: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *wsServer) Start() {
|
||||
s.log.Debug("WebInfoServer starting...")
|
||||
s.Running = true
|
||||
go func() {
|
||||
var addr string
|
||||
if *s.localhostOnly {
|
||||
addr = fmt.Sprintf("localhost:%d", *s.port)
|
||||
} else {
|
||||
addr = fmt.Sprintf("0.0.0.0:%d", *s.port)
|
||||
}
|
||||
s.Server = &http.Server{
|
||||
Addr: addr,
|
||||
Handler: s.mux,
|
||||
}
|
||||
err := s.Server.ListenAndServe()
|
||||
s.Running = false
|
||||
if errors.Is(err, http.ErrServerClosed) {
|
||||
s.log.Info("WebInfoServer closed")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
s.log.Errorf("Failed to start webinfo server: %s", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *wsServer) Stop() error {
|
||||
s.log.Debug("WebInfoServer stopping...")
|
||||
s.lock.Lock()
|
||||
s.clients = make(map[*wsClient]bool)
|
||||
s.lock.Unlock()
|
||||
if s.Server != nil {
|
||||
return s.Server.Shutdown(context.TODO())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *wsServer) getWsUrl() string {
|
||||
if *s.localhostOnly {
|
||||
return fmt.Sprintf("ws://localhost:%d/wsinfo", *s.port)
|
||||
}
|
||||
return fmt.Sprintf("ws://0.0.0.0:%d/wsinfo", *s.port)
|
||||
}
|
||||
180
plugin/wshub/wshub.go
Normal file
180
plugin/wshub/wshub.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package wshub
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui"
|
||||
"AynaLivePlayer/gui/component"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"AynaLivePlayer/pkg/logger"
|
||||
"encoding/json"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/data/binding"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type WsHub struct {
|
||||
config.BaseConfig
|
||||
Enabled bool
|
||||
Port int
|
||||
LocalHostOnly bool
|
||||
panel fyne.CanvasObject
|
||||
server *wsServer
|
||||
log logger.ILogger
|
||||
}
|
||||
|
||||
func NewWsHub() *WsHub {
|
||||
return &WsHub{
|
||||
Enabled: false,
|
||||
Port: 29629,
|
||||
LocalHostOnly: true,
|
||||
log: global.Logger.WithPrefix("plugin.wshub"),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WsHub) Enable() error {
|
||||
config.LoadConfig(w)
|
||||
w.server = newWsServer(&w.Port, &w.LocalHostOnly)
|
||||
gui.AddConfigLayout(w)
|
||||
w.registerEvents()
|
||||
w.log.Info("webinfo loaded")
|
||||
if w.Enabled {
|
||||
w.log.Info("starting web backend server")
|
||||
w.server.Start()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WsHub) Disable() error {
|
||||
if w.server.Running {
|
||||
err := w.server.Stop()
|
||||
if err != nil {
|
||||
w.log.Warnf("stop server have error: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WsHub) Name() string {
|
||||
return "WsHub"
|
||||
}
|
||||
|
||||
func (w *WsHub) Title() string {
|
||||
return i18n.T("plugin.wshub.title")
|
||||
}
|
||||
|
||||
func (w *WsHub) Description() string {
|
||||
return i18n.T("plugin.wshub.description")
|
||||
}
|
||||
|
||||
func (w *WsHub) CreatePanel() fyne.CanvasObject {
|
||||
if w.panel != nil {
|
||||
return w.panel
|
||||
}
|
||||
statusText := widget.NewLabel("")
|
||||
freshStatusText := func() {
|
||||
if w.server.Running {
|
||||
statusText.SetText(i18n.T("plugin.wshub.server_status.running"))
|
||||
return
|
||||
} else {
|
||||
statusText.SetText(i18n.T("plugin.wshub.server_status.stopped"))
|
||||
}
|
||||
}
|
||||
serverStatus := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("plugin.wshub.server_status")),
|
||||
statusText,
|
||||
)
|
||||
autoStart := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("plugin.wshub.autostart")),
|
||||
component.NewCheckOneWayBinding("", &w.Enabled, w.Enabled))
|
||||
localHostOnly := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("plugin.wshub.local_host_only")),
|
||||
component.NewCheckOneWayBinding("", &w.LocalHostOnly, w.LocalHostOnly))
|
||||
freshStatusText()
|
||||
serverPort := container.NewBorder(nil, nil,
|
||||
widget.NewLabel(i18n.T("plugin.wshub.port")), nil,
|
||||
widget.NewEntryWithData(binding.IntToString(binding.BindInt(&w.Port))),
|
||||
)
|
||||
serverUrl := widget.NewEntry()
|
||||
serverUrl.SetText(w.server.getWsUrl())
|
||||
serverUrl.Disable()
|
||||
serverPreview := container.NewBorder(nil, nil,
|
||||
widget.NewLabel(i18n.T("plugin.wshub.server_link")), nil,
|
||||
serverUrl,
|
||||
)
|
||||
refreshServerUrl := func() {
|
||||
serverUrl.SetText(w.server.getWsUrl())
|
||||
}
|
||||
stopBtn := component.NewAsyncButtonWithIcon(
|
||||
i18n.T("plugin.wshub.server_control.stop"),
|
||||
theme.MediaStopIcon(),
|
||||
func() {
|
||||
if !w.server.Running {
|
||||
return
|
||||
}
|
||||
w.log.Info("User try stop webinfo server")
|
||||
err := w.server.Stop()
|
||||
if err != nil {
|
||||
w.log.Warnf("stop server have error: %s", err)
|
||||
}
|
||||
freshStatusText()
|
||||
},
|
||||
)
|
||||
startBtn := component.NewAsyncButtonWithIcon(
|
||||
i18n.T("plugin.wshub.server_control.start"),
|
||||
theme.MediaPlayIcon(),
|
||||
func() {
|
||||
if w.server.Running {
|
||||
return
|
||||
}
|
||||
w.log.Infof("User try start webinfo server with port %d", w.Port)
|
||||
w.server.Start()
|
||||
freshStatusText()
|
||||
refreshServerUrl()
|
||||
},
|
||||
)
|
||||
restartBtn := component.NewAsyncButtonWithIcon(
|
||||
i18n.T("plugin.wshub.server_control.restart"),
|
||||
theme.MediaReplayIcon(),
|
||||
func() {
|
||||
w.log.Infof("User try restart webinfo server with port %d", w.Port)
|
||||
if w.server.Running {
|
||||
if err := w.server.Stop(); err != nil {
|
||||
w.log.Warnf("stop server have error: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
w.server.Start()
|
||||
freshStatusText()
|
||||
refreshServerUrl()
|
||||
},
|
||||
)
|
||||
ctrlBtns := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("plugin.wshub.server_control")),
|
||||
startBtn, stopBtn, restartBtn,
|
||||
)
|
||||
w.panel = container.NewVBox(serverStatus, autoStart, localHostOnly, serverPreview, serverPort, ctrlBtns)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WsHub) registerEvents() {
|
||||
for eid, _ := range events.EventsMapping {
|
||||
global.EventManager.RegisterA(eid,
|
||||
"plugin.wshub.event."+string(eid),
|
||||
func(e *event.Event) {
|
||||
val, err := json.Marshal(EventData{
|
||||
EventID: e.Id,
|
||||
Data: e.Data,
|
||||
})
|
||||
if err != nil {
|
||||
w.log.Errorf("failed to marshal event data %v", err)
|
||||
return
|
||||
}
|
||||
w.server.broadcast(val)
|
||||
})
|
||||
}
|
||||
}
|
||||
8
todo.txt
8
todo.txt
@@ -7,6 +7,7 @@
|
||||
|
||||
- 适配歌词服务器
|
||||
- 媒体源 - 歌单信息获取
|
||||
- mpris, SMTC
|
||||
- web弹幕协议的断线handler (web 重连)
|
||||
- 歌词event发送全部歌词,前端处理不同版本
|
||||
- 网页输出重写,使用网页版本,不绑定在点歌机内(点歌机不需要启动网页服务)
|
||||
@@ -14,7 +15,12 @@
|
||||
----
|
||||
|
||||
Finished
|
||||
- 2024.04.22 : 文本输出, 歌曲最长时长控制,bug修复, 网易云登录(歌曲来源统一登录)
|
||||
- 2024.05.06@1.0.6 : 修复若干bug
|
||||
- 2024.04.30 : 完成websocket hub
|
||||
- 2024.04.28 : 修复id点歌匹配失败的问题, 修复黑名单会被id绕过的bug
|
||||
- 2024.04.26@1.0.5: 修复直播间长连接重复连接导致点歌重复点的问题,修复直播间添加失败时候会触发闪退的问题,更新依赖导致的闪退问题,修复webdm断开再连接之后无法获取到弹幕的问题
|
||||
- 2024.04.24@1.0.4: 添加log,修复闪退
|
||||
- 2024.04.22@1.0.2: 文本输出, 歌曲最长时长控制,bug修复, 网易云登录(歌曲来源统一登录)
|
||||
- 2024.04.17 : 1. 弹幕拿不到的问题,尽量使用身份码,网页协议随时可能爆炸
|
||||
2. 酷我音乐复活
|
||||
3. 黑名单
|
||||
|
||||
Reference in New Issue
Block a user