mirror of
https://github.com/AynaLivePlayer/AynaLivePlayer.git
synced 2025-12-06 10:22:50 +08:00
74
go.mod
74
go.mod
@@ -1,36 +1,39 @@
|
||||
module AynaLivePlayer
|
||||
|
||||
go 1.19
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.4
|
||||
|
||||
replace (
|
||||
github.com/AynaLivePlayer/liveroom-sdk v0.1.0 => ./pkg/liveroom-sdk // submodule
|
||||
github.com/AynaLivePlayer/miaosic v0.1.5 => ./pkg/miaosic // submodule
|
||||
github.com/AynaLivePlayer/miaosic v0.1.7 => ./pkg/miaosic // submodule
|
||||
|
||||
github.com/saltosystems/winrt-go => github.com/go-musicfox/winrt-go v0.1.4 // winrt with media foundation
|
||||
)
|
||||
|
||||
require (
|
||||
fyne.io/fyne/v2 v2.5.4
|
||||
fyne.io/fyne/v2 v2.6.1
|
||||
github.com/AynaLivePlayer/liveroom-sdk v0.1.0
|
||||
github.com/AynaLivePlayer/miaosic v0.1.5
|
||||
github.com/AynaLivePlayer/miaosic v0.1.7
|
||||
github.com/adrg/libvlc-go/v3 v3.1.6
|
||||
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.1
|
||||
github.com/aynakeya/go-mpv v0.0.6
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a
|
||||
github.com/aynakeya/go-mpv v0.0.7
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728
|
||||
github.com/go-ole/go-ole v1.3.0
|
||||
github.com/go-resty/resty/v2 v2.7.0
|
||||
github.com/go-resty/resty/v2 v2.16.5
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/mattn/go-colorable v0.1.12
|
||||
github.com/mattn/go-colorable v0.1.14
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||
github.com/saltosystems/winrt-go v0.0.0-20240320184339-289d313a74b7
|
||||
github.com/saltosystems/winrt-go v0.0.0-20241223121953-98e32661f6ff
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/tidwall/gjson v1.17.3
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tidwall/gjson v1.18.0
|
||||
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459
|
||||
go.uber.org/zap v1.26.0
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
|
||||
golang.org/x/sys v0.25.0
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b
|
||||
golang.org/x/sys v0.33.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
)
|
||||
|
||||
@@ -38,45 +41,46 @@ require (
|
||||
fyne.io/systray v1.11.0 // indirect
|
||||
github.com/AynaLivePlayer/blivedm-go v0.0.0-20250527143915-74cc4b2603bc // indirect
|
||||
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||
github.com/PuerkitoBio/goquery v1.7.1 // indirect
|
||||
github.com/PuerkitoBio/goquery v1.10.3 // indirect
|
||||
github.com/XiaoMengXinX/Music163Api-Go v0.1.30 // indirect
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/andybalholm/cascadia v1.2.0 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/aynakeya/deepcolor v1.0.3 // 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/dhowden/tag v0.0.0-20240417053706-3d75831295e8 // indirect
|
||||
github.com/fredbi/uri v1.1.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect
|
||||
github.com/fyne-io/glfw-js v0.0.0-20241126112943-313d8a0fe1d0 // 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/fyne-io/gl-js v0.1.0 // indirect
|
||||
github.com/fyne-io/glfw-js v0.2.0 // indirect
|
||||
github.com/fyne-io/image v0.1.1 // indirect
|
||||
github.com/fyne-io/oksvg v0.1.0 // indirect
|
||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect
|
||||
github.com/go-text/render v0.2.0 // indirect
|
||||
github.com/go-text/typesetting v0.2.0 // indirect
|
||||
github.com/go-text/typesetting v0.2.1 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/google/uuid v1.5.0 // indirect
|
||||
github.com/gopherjs/gopherjs v1.17.2 // indirect
|
||||
github.com/jeandeaual/go-locale v0.0.0-20240223122105-ce5225dcaa49 // indirect
|
||||
github.com/hack-pad/go-indexeddb v0.3.2 // indirect
|
||||
github.com/hack-pad/safejs v0.1.0 // indirect
|
||||
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 // indirect
|
||||
github.com/jinzhu/copier v0.4.0 // indirect
|
||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.0 // indirect
|
||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rymdport/portal v0.3.0 // indirect
|
||||
github.com/sahilm/fuzzy v0.1.0 // indirect
|
||||
github.com/rymdport/portal v0.4.1 // indirect
|
||||
github.com/sahilm/fuzzy v0.1.1 // indirect
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/cast v1.9.2 // indirect
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/yuin/goldmark v1.7.1 // indirect
|
||||
github.com/yuin/goldmark v1.7.8 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/image v0.18.0 // indirect
|
||||
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/image v0.24.0 // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -37,7 +37,8 @@ func (b *AsyncButton) SetOnTapped(f func()) {
|
||||
b.Disable()
|
||||
go func() {
|
||||
f()
|
||||
b.Enable()
|
||||
//time.Sleep(3 * time.Second)
|
||||
fyne.Do(b.Enable)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/component"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
@@ -44,10 +45,10 @@ func (b *bascicConfig) CreatePanel() fyne.CanvasObject {
|
||||
})
|
||||
global.EventManager.RegisterA(events.PlaylistModeChangeUpdate(model.PlaylistIDPlayer),
|
||||
"gui.config.basic.random_playlist.player",
|
||||
func(event *event.Event) {
|
||||
gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
data := event.Data.(events.PlaylistModeChangeUpdateEvent)
|
||||
playerRandomCheck.SetChecked(data.Mode == model.PlaylistModeRandom)
|
||||
})
|
||||
}))
|
||||
|
||||
systemRandomCheck := widget.NewCheck(i18n.T("gui.config.basic.random_playlist.system"),
|
||||
func(b bool) {
|
||||
@@ -63,10 +64,10 @@ func (b *bascicConfig) CreatePanel() fyne.CanvasObject {
|
||||
|
||||
global.EventManager.RegisterA(events.PlaylistModeChangeUpdate(model.PlaylistIDSystem),
|
||||
"gui.config.basic.random_playlist.system",
|
||||
func(event *event.Event) {
|
||||
gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
data := event.Data.(events.PlaylistModeChangeUpdateEvent)
|
||||
systemRandomCheck.SetChecked(data.Mode == model.PlaylistModeRandom)
|
||||
})
|
||||
}))
|
||||
|
||||
randomPlaylist := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("gui.config.basic.random_playlist")),
|
||||
@@ -86,7 +87,7 @@ func (b *bascicConfig) CreatePanel() fyne.CanvasObject {
|
||||
global.EventManager.RegisterA(
|
||||
events.PlayerAudioDeviceUpdate,
|
||||
"gui.config.basic.audio_device.update",
|
||||
func(event *event.Event) {
|
||||
gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
data := event.Data.(events.PlayerAudioDeviceUpdateEvent)
|
||||
devices := make([]string, len(data.Devices))
|
||||
deviceDesc2Name = make(map[string]string)
|
||||
@@ -102,7 +103,7 @@ func (b *bascicConfig) CreatePanel() fyne.CanvasObject {
|
||||
deviceSel.Options = devices
|
||||
deviceSel.Selected = currentDevice
|
||||
deviceSel.Refresh()
|
||||
})
|
||||
}))
|
||||
|
||||
outputDevice := container.NewBorder(nil, nil,
|
||||
widget.NewLabel(i18n.T("gui.config.basic.audio_device")), nil,
|
||||
|
||||
16
gui/gui.go
16
gui/gui.go
@@ -4,6 +4,7 @@ import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"os"
|
||||
|
||||
_logger "AynaLivePlayer/pkg/logger"
|
||||
)
|
||||
@@ -33,7 +35,9 @@ func Initialize() {
|
||||
logger = global.Logger.WithPrefix("GUI")
|
||||
black_magic()
|
||||
logger.Info("Initializing GUI")
|
||||
//os.Setenv("FYNE_FONT", config.GetAssetPath("msyh.ttc"))
|
||||
if config.General.CustomFonts != "" {
|
||||
_ = os.Setenv("FYNE_FONT", config.GetAssetPath(config.General.CustomFonts))
|
||||
}
|
||||
App = app.NewWithID(config.ProgramName)
|
||||
//App.Settings().SetTheme(&myTheme{})
|
||||
MainWindow = App.NewWindow(fmt.Sprintf("%s Ver %s", config.ProgramName, model.Version(config.Version)))
|
||||
@@ -65,18 +69,20 @@ func Initialize() {
|
||||
//MainWindow.Resize(fyne.NewSize(1280, 720))
|
||||
MainWindow.Resize(fyne.NewSize(config.General.Width, config.General.Height))
|
||||
|
||||
setupPlayerWindow()
|
||||
// todo: fix, window were created even if not show. this block gui from closing
|
||||
// i can't create sub window before the main window shows.
|
||||
// setupPlayerWindow()
|
||||
|
||||
// register error
|
||||
global.EventManager.RegisterA(
|
||||
events.ErrorUpdate, "gui.show_error", func(e *event.Event) {
|
||||
events.ErrorUpdate, "gui.show_error", gutil.ThreadSafeHandler(func(e *event.Event) {
|
||||
err := e.Data.(events.ErrorUpdateEvent).Error
|
||||
logger.Warnf("gui received error event: %v, %v", err, err == nil)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
dialog.ShowError(err, MainWindow)
|
||||
})
|
||||
}))
|
||||
|
||||
checkUpdate()
|
||||
MainWindow.SetFixedSize(config.General.FixedSize)
|
||||
@@ -91,7 +97,9 @@ func Initialize() {
|
||||
})
|
||||
}
|
||||
MainWindow.SetOnClosed(func() {
|
||||
logger.Infof("GUI closing")
|
||||
if playerWindow != nil {
|
||||
logger.Infof("player window closing")
|
||||
playerWindow.Close()
|
||||
}
|
||||
})
|
||||
|
||||
15
gui/gutil/fyne.go
Normal file
15
gui/gutil/fyne.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package gutil
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
// since 2.6.1, calls to fyne API from other go routine must be wrapped in fyne.Do
|
||||
func ThreadSafeHandler(fn func(e *event.Event)) func(e *event.Event) {
|
||||
return func(e *event.Event) {
|
||||
fyne.Do(func() {
|
||||
fn(e)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"fmt"
|
||||
@@ -76,10 +77,10 @@ func createHistoryList() fyne.CanvasObject {
|
||||
func registerHistoryHandler() {
|
||||
global.EventManager.RegisterA(
|
||||
events.PlaylistDetailUpdate(model.PlaylistIDHistory),
|
||||
"gui.history.update", func(event *event.Event) {
|
||||
"gui.history.update", gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
History.mux.Lock()
|
||||
History.Medias = event.Data.(events.PlaylistDetailUpdateEvent).Medias
|
||||
History.List.Refresh()
|
||||
History.mux.Unlock()
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/gui/xfyne"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
@@ -135,14 +136,14 @@ func registerRoomHandlers() {
|
||||
global.EventManager.RegisterA(
|
||||
events.LiveRoomProviderUpdate,
|
||||
"gui.liveroom.provider_update",
|
||||
func(event *event.Event) {
|
||||
gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
RoomTab.providers = event.Data.(events.LiveRoomProviderUpdateEvent).Providers
|
||||
RoomTab.Rooms.Refresh()
|
||||
})
|
||||
}))
|
||||
global.EventManager.RegisterA(
|
||||
events.LiveRoomRoomsUpdate,
|
||||
"gui.liveroom.rooms_update",
|
||||
func(event *event.Event) {
|
||||
gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
logger.Infof("Update rooms")
|
||||
data := event.Data.(events.LiveRoomRoomsUpdateEvent)
|
||||
RoomTab.lock.Lock()
|
||||
@@ -150,11 +151,11 @@ func registerRoomHandlers() {
|
||||
RoomTab.Rooms.Select(0)
|
||||
RoomTab.Rooms.Refresh()
|
||||
RoomTab.lock.Unlock()
|
||||
})
|
||||
}))
|
||||
global.EventManager.RegisterA(
|
||||
events.LiveRoomStatusUpdate,
|
||||
"gui.liveroom.room_status_update",
|
||||
func(event *event.Event) {
|
||||
gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
room := event.Data.(events.LiveRoomStatusUpdateEvent).Room
|
||||
index := -1
|
||||
for i := 0; i < len(RoomTab.rooms); i++ {
|
||||
@@ -182,7 +183,7 @@ func registerRoomHandlers() {
|
||||
}
|
||||
RoomTab.Status.Refresh()
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
@@ -216,10 +217,10 @@ func createRoomController() fyne.CanvasObject {
|
||||
global.EventManager.RegisterA(
|
||||
events.LiveRoomOperationFinish,
|
||||
"gui.liveroom.operation_finish",
|
||||
func(event *event.Event) {
|
||||
gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
RoomTab.ConnectBtn.Enable()
|
||||
RoomTab.DisConnectBtn.Enable()
|
||||
})
|
||||
}))
|
||||
RoomTab.Status = widget.NewLabel(i18n.T("gui.room.waiting"))
|
||||
RoomTab.RoomTitle = widget.NewLabel("")
|
||||
RoomTab.RoomID = widget.NewLabel("")
|
||||
|
||||
@@ -59,7 +59,7 @@ func registerPlayControllerHandler() {
|
||||
PlayController.ButtonLrc.OnTapped = func() {
|
||||
if !PlayController.LrcWindowOpen {
|
||||
PlayController.LrcWindowOpen = true
|
||||
createLyricWindow().Show()
|
||||
createLyricWindow().Close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,26 +67,25 @@ func registerPlayControllerHandler() {
|
||||
showPlayerWindow()
|
||||
}
|
||||
|
||||
global.EventManager.RegisterA(events.PlayerPropertyPauseUpdate, "gui.player.controller.paused", func(event *event.Event) {
|
||||
global.EventManager.RegisterA(events.PlayerPropertyPauseUpdate, "gui.player.controller.paused", gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
if event.Data.(events.PlayerPropertyPauseUpdateEvent).Paused {
|
||||
PlayController.ButtonSwitch.Icon = theme.MediaPlayIcon()
|
||||
} else {
|
||||
PlayController.ButtonSwitch.Icon = theme.MediaPauseIcon()
|
||||
}
|
||||
PlayController.ButtonSwitch.Refresh()
|
||||
})
|
||||
}))
|
||||
|
||||
global.EventManager.RegisterA(events.PlayerPropertyPercentPosUpdate, "gui.player.controller.percent_pos", func(event *event.Event) {
|
||||
global.EventManager.RegisterA(events.PlayerPropertyPercentPosUpdate, "gui.player.controller.percent_pos", gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
if PlayController.Progress.Dragging {
|
||||
return
|
||||
}
|
||||
PlayController.Progress.Value = event.Data.(events.PlayerPropertyPercentPosUpdateEvent).PercentPos * 10
|
||||
PlayController.Progress.Refresh()
|
||||
})
|
||||
}))
|
||||
|
||||
global.EventManager.RegisterA(events.PlayerPropertyIdleActiveUpdate, "gui.player.controller.idle_active", func(event *event.Event) {
|
||||
global.EventManager.RegisterA(events.PlayerPropertyIdleActiveUpdate, "gui.player.controller.idle_active", gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
isIdle := event.Data.(events.PlayerPropertyIdleActiveUpdateEvent).IsIdle
|
||||
// todo: @3
|
||||
if isIdle {
|
||||
PlayController.Progress.Value = 0
|
||||
PlayController.Progress.Max = 0
|
||||
@@ -97,7 +96,7 @@ func registerPlayControllerHandler() {
|
||||
} else {
|
||||
PlayController.Progress.Max = 1000
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
PlayController.Progress.Max = 0
|
||||
PlayController.Progress.OnDragEnd = func(f float64) {
|
||||
@@ -107,18 +106,18 @@ func registerPlayControllerHandler() {
|
||||
})
|
||||
}
|
||||
|
||||
global.EventManager.RegisterA(events.PlayerPropertyTimePosUpdate, "gui.player.controller.time_pos", func(event *event.Event) {
|
||||
global.EventManager.RegisterA(events.PlayerPropertyTimePosUpdate, "gui.player.controller.time_pos", gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
PlayController.CurrentTime.SetText(util.FormatTime(int(event.Data.(events.PlayerPropertyTimePosUpdateEvent).TimePos)))
|
||||
})
|
||||
}))
|
||||
|
||||
global.EventManager.RegisterA(events.PlayerPropertyDurationUpdate, "gui.player.controller.duration", func(event *event.Event) {
|
||||
global.EventManager.RegisterA(events.PlayerPropertyDurationUpdate, "gui.player.controller.duration", gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
PlayController.TotalTime.SetText(util.FormatTime(int(event.Data.(events.PlayerPropertyDurationUpdateEvent).Duration)))
|
||||
})
|
||||
}))
|
||||
|
||||
global.EventManager.RegisterA(events.PlayerPropertyVolumeUpdate, "gui.player.controller.volume", func(event *event.Event) {
|
||||
global.EventManager.RegisterA(events.PlayerPropertyVolumeUpdate, "gui.player.controller.volume", gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
PlayController.Volume.Value = event.Data.(events.PlayerPropertyVolumeUpdateEvent).Volume
|
||||
PlayController.Volume.Refresh()
|
||||
})
|
||||
}))
|
||||
|
||||
PlayController.Volume.OnChanged = func(f float64) {
|
||||
global.EventManager.CallA(events.PlayerVolumeChangeCmd, events.PlayerVolumeChangeCmdEvent{
|
||||
@@ -126,7 +125,8 @@ func registerPlayControllerHandler() {
|
||||
})
|
||||
}
|
||||
|
||||
global.EventManager.RegisterA(events.PlayerPlayingUpdate, "gui.player.updateinfo", func(event *event.Event) {
|
||||
// todo: double check cover loading for new thread model
|
||||
global.EventManager.RegisterA(events.PlayerPlayingUpdate, "gui.player.updateinfo", gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
if event.Data.(events.PlayerPlayingUpdateEvent).Removed {
|
||||
PlayController.Progress.Value = 0
|
||||
PlayController.Progress.Max = 0
|
||||
@@ -176,12 +176,12 @@ func registerPlayControllerHandler() {
|
||||
return
|
||||
}
|
||||
PlayController.Cover.Resource = pic.Resource
|
||||
PlayController.Cover.Refresh()
|
||||
fyne.Do(PlayController.Cover.Refresh)
|
||||
}
|
||||
|
||||
}()
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
func createPlayControllerV2() fyne.CanvasObject {
|
||||
|
||||
@@ -3,6 +3,7 @@ package gui
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"fyne.io/fyne/v2"
|
||||
@@ -41,8 +42,9 @@ func createLyricWindow() fyne.Window {
|
||||
w.CenterOnScreen()
|
||||
|
||||
// register handlers
|
||||
// todo: lyric not update correctly, known bug https://github.com/fyne-io/fyne/pull/5783
|
||||
global.EventManager.RegisterA(
|
||||
events.PlayerLyricPosUpdate, "player.lyric.current_lyric", func(event *event.Event) {
|
||||
events.PlayerLyricPosUpdate, "player.lyric.current_lyric", gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
e := event.Data.(events.PlayerLyricPosUpdateEvent)
|
||||
logger.Debug("lyric update", e)
|
||||
if prevIndex >= len(fullLrc.Objects) || e.CurrentIndex >= len(fullLrc.Objects) {
|
||||
@@ -66,13 +68,13 @@ func createLyricWindow() fyne.Window {
|
||||
},
|
||||
})
|
||||
fullLrc.Refresh()
|
||||
})
|
||||
}))
|
||||
|
||||
global.EventManager.RegisterA(events.PlayerLyricReload, "player.lyric.current_lyric", func(event *event.Event) {
|
||||
global.EventManager.RegisterA(events.PlayerLyricReload, "player.lyric.current_lyric", gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
e := event.Data.(events.PlayerLyricReloadEvent)
|
||||
fullLrc.Objects = createLyricObj(&e.Lyrics)
|
||||
lrcWindow.Refresh()
|
||||
})
|
||||
}))
|
||||
|
||||
global.EventManager.CallA(events.PlayerLyricRequestCmd, events.PlayerLyricRequestCmdEvent{})
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"fmt"
|
||||
@@ -74,12 +75,12 @@ func createPlaylist() fyne.CanvasObject {
|
||||
object.(*fyne.Container).Objects[1].(*widget.Label).SetText(fmt.Sprintf("%d", id))
|
||||
object.(*fyne.Container).Objects[2].(*playlistOperationButton).Index = id
|
||||
})
|
||||
global.EventManager.RegisterA(events.PlaylistDetailUpdate(model.PlaylistIDPlayer), "gui.player.playlist.update", func(event *event.Event) {
|
||||
global.EventManager.RegisterA(events.PlaylistDetailUpdate(model.PlaylistIDPlayer), "gui.player.playlist.update", gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
UserPlaylist.mux.Lock()
|
||||
UserPlaylist.Medias = event.Data.(events.PlaylistDetailUpdateEvent).Medias
|
||||
UserPlaylist.List.Refresh()
|
||||
UserPlaylist.mux.Unlock()
|
||||
})
|
||||
}))
|
||||
return container.NewBorder(
|
||||
container.NewBorder(nil, nil,
|
||||
widget.NewLabel("#"), widget.NewLabel(i18n.T("gui.player.playlist.ops")),
|
||||
|
||||
@@ -17,6 +17,9 @@ func setupPlayerWindow() {
|
||||
}
|
||||
|
||||
func showPlayerWindow() {
|
||||
if playerWindow == nil {
|
||||
setupPlayerWindow()
|
||||
}
|
||||
playerWindow.Show()
|
||||
if playerWindowHandle == 0 {
|
||||
playerWindowHandle = xfyne.GetWindowHandle(playerWindow)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/gui/xfyne"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
@@ -97,14 +98,14 @@ func createPlaylists() fyne.CanvasObject {
|
||||
})
|
||||
}
|
||||
global.EventManager.RegisterA(events.MediaProviderUpdate,
|
||||
"gui.playlists.provider.update", func(event *event.Event) {
|
||||
"gui.playlists.provider.update", gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
providers := event.Data.(events.MediaProviderUpdateEvent)
|
||||
s := make([]string, len(providers.Providers))
|
||||
copy(s, providers.Providers)
|
||||
PlaylistManager.providers = s
|
||||
})
|
||||
}))
|
||||
global.EventManager.RegisterA(events.PlaylistManagerInfoUpdate,
|
||||
"gui.playlists.info.update", func(event *event.Event) {
|
||||
"gui.playlists.info.update", gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
data := event.Data.(events.PlaylistManagerInfoUpdateEvent)
|
||||
prevLen := len(PlaylistManager.currentPlaylists)
|
||||
PlaylistManager.currentPlaylists = data.Playlists
|
||||
@@ -113,12 +114,12 @@ func createPlaylists() fyne.CanvasObject {
|
||||
if prevLen != len(PlaylistManager.currentPlaylists) {
|
||||
PlaylistManager.Playlists.Select(0)
|
||||
}
|
||||
})
|
||||
}))
|
||||
global.EventManager.RegisterA(events.PlaylistManagerSystemUpdate,
|
||||
"gui.playlists.system.update", func(event *event.Event) {
|
||||
"gui.playlists.system.update", gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
data := event.Data.(events.PlaylistManagerSystemUpdateEvent)
|
||||
PlaylistManager.CurrentSystemPlaylist.SetText(i18n.T("gui.playlist.current") + data.Info.DisplayName())
|
||||
})
|
||||
}))
|
||||
return container.NewHBox(
|
||||
container.NewBorder(
|
||||
nil, container.NewCenter(container.NewHBox(PlaylistManager.AddBtn, PlaylistManager.RemoveBtn)),
|
||||
@@ -190,12 +191,12 @@ func createPlaylistMedias() fyne.CanvasObject {
|
||||
}
|
||||
})
|
||||
global.EventManager.RegisterA(events.PlaylistManagerCurrentUpdate,
|
||||
"gui.playlists.current.update", func(event *event.Event) {
|
||||
"gui.playlists.current.update", gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
logger.Infof("receive current playlist update, try to refresh playlist medias")
|
||||
data := event.Data.(events.PlaylistManagerCurrentUpdateEvent)
|
||||
PlaylistManager.currentMedias = data.Medias
|
||||
PlaylistManager.PlaylistMedia.Refresh()
|
||||
})
|
||||
}))
|
||||
return container.NewBorder(
|
||||
container.NewHBox(PlaylistManager.RefreshBtn, PlaylistManager.SetAsSystemBtn, PlaylistManager.CurrentSystemPlaylist), nil,
|
||||
nil, nil,
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/component"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"fyne.io/fyne/v2"
|
||||
@@ -14,7 +15,7 @@ import (
|
||||
|
||||
var SearchBar = &struct {
|
||||
Input *component.Entry
|
||||
Button *component.AsyncButton
|
||||
Button *widget.Button
|
||||
UseSource *widget.Select
|
||||
}{}
|
||||
|
||||
@@ -26,7 +27,7 @@ func createSearchBar() fyne.CanvasObject {
|
||||
SearchBar.Button.OnTapped()
|
||||
}
|
||||
}
|
||||
SearchBar.Button = component.NewAsyncButton(i18n.T("gui.search.search"), func() {
|
||||
SearchBar.Button = widget.NewButton(i18n.T("gui.search.search"), func() {
|
||||
keyword := SearchBar.Input.Text
|
||||
pr := SearchBar.UseSource.Selected
|
||||
logger.Debugf("Search keyword: %s, provider: %s", keyword, pr)
|
||||
@@ -41,7 +42,7 @@ func createSearchBar() fyne.CanvasObject {
|
||||
})
|
||||
|
||||
global.EventManager.RegisterA(events.MediaProviderUpdate,
|
||||
"gui.search.provider.update", func(event *event.Event) {
|
||||
"gui.search.provider.update", gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
providers := event.Data.(events.MediaProviderUpdateEvent)
|
||||
s := make([]string, len(providers.Providers))
|
||||
copy(s, providers.Providers)
|
||||
@@ -49,7 +50,7 @@ func createSearchBar() fyne.CanvasObject {
|
||||
if len(s) > 0 {
|
||||
SearchBar.UseSource.SetSelected(s[0])
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
SearchBar.UseSource = widget.NewSelect([]string{}, func(s string) {
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"fmt"
|
||||
@@ -61,13 +62,13 @@ func createSearchList() fyne.CanvasObject {
|
||||
})
|
||||
}
|
||||
})
|
||||
global.EventManager.RegisterA(events.SearchResultUpdate, "gui.search.update_result", func(event *event.Event) {
|
||||
global.EventManager.RegisterA(events.SearchResultUpdate, "gui.search.update_result", gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
items := event.Data.(events.SearchResultUpdateEvent).Medias
|
||||
SearchResult.Items = items
|
||||
SearchResult.mux.Lock()
|
||||
SearchResult.List.Refresh()
|
||||
SearchResult.mux.Unlock()
|
||||
})
|
||||
}))
|
||||
return container.NewBorder(
|
||||
container.NewBorder(nil, nil,
|
||||
widget.NewLabel("#"), widget.NewLabel(i18n.T("gui.search.operation")),
|
||||
|
||||
@@ -3,6 +3,7 @@ package gui
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
@@ -11,7 +12,7 @@ import (
|
||||
|
||||
func checkUpdate() {
|
||||
global.EventManager.RegisterA(
|
||||
events.CheckUpdateResultUpdate, "gui.updater.check_update", func(event *event.Event) {
|
||||
events.CheckUpdateResultUpdate, "gui.updater.check_update", gutil.ThreadSafeHandler(func(event *event.Event) {
|
||||
data := event.Data.(events.CheckUpdateResultUpdateEvent)
|
||||
msg := data.Info.Version.String() + "\n\n\n" + data.Info.Info
|
||||
if data.HasUpdate {
|
||||
@@ -27,5 +28,5 @@ func checkUpdate() {
|
||||
widget.NewRichTextFromMarkdown(""),
|
||||
MainWindow)
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
// getGlfwWindow returns the glfw.Window pointer from a fyne.Window.
|
||||
// very unsafe and ugly hacks. but it works.
|
||||
// todo: replace with LifeCycle https://github.com/fyne-io/fyne/issues/4483
|
||||
func getGlfwWindow(window fyne.Window) *glfw.Window {
|
||||
rv := reflect.ValueOf(window)
|
||||
if rv.Type().String() != "*glfw.window" {
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
package player
|
||||
|
||||
import "AynaLivePlayer/internal/player/mpv"
|
||||
import (
|
||||
"AynaLivePlayer/internal/player/mpv"
|
||||
"AynaLivePlayer/internal/player/vlc"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
)
|
||||
|
||||
func SetupMpvPlayer() {
|
||||
mpv.SetupPlayer()
|
||||
if config.Experimental.PlayerCore == "vlc" {
|
||||
vlc.SetupPlayer()
|
||||
} else {
|
||||
mpv.SetupPlayer()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func StopMpvPlayer() {
|
||||
mpv.StopPlayer()
|
||||
if config.Experimental.PlayerCore == "vlc" {
|
||||
vlc.StopPlayer()
|
||||
} else {
|
||||
mpv.StopPlayer()
|
||||
}
|
||||
}
|
||||
|
||||
46
internal/player/vlc/config.go
Normal file
46
internal/player/vlc/config.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package vlc
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
)
|
||||
|
||||
type playerConfig struct {
|
||||
Volume float64
|
||||
AudioDevice string
|
||||
DisplayMusicCover bool
|
||||
}
|
||||
|
||||
func (p *playerConfig) Name() string {
|
||||
return "Player"
|
||||
}
|
||||
|
||||
func (p *playerConfig) OnLoad() {
|
||||
return
|
||||
}
|
||||
|
||||
func (p *playerConfig) OnSave() {
|
||||
return
|
||||
}
|
||||
|
||||
var cfg = &playerConfig{
|
||||
Volume: 100,
|
||||
DisplayMusicCover: true,
|
||||
}
|
||||
|
||||
func restoreConfig() {
|
||||
global.EventManager.CallA(events.PlayerVolumeChangeCmd, events.PlayerVolumeChangeCmdEvent{
|
||||
Volume: cfg.Volume,
|
||||
})
|
||||
global.EventManager.RegisterA(events.PlayerPropertyVolumeUpdate, "player.config.volume", func(evnt *event.Event) {
|
||||
data := evnt.Data.(events.PlayerPropertyVolumeUpdateEvent)
|
||||
if data.Volume < 0 {
|
||||
return
|
||||
}
|
||||
cfg.Volume = data.Volume
|
||||
})
|
||||
global.EventManager.CallA(events.PlayerSetAudioDeviceCmd, events.PlayerSetAudioDeviceCmdEvent{
|
||||
Device: cfg.AudioDevice,
|
||||
})
|
||||
}
|
||||
443
internal/player/vlc/vlc.go
Normal file
443
internal/player/vlc/vlc.go
Normal file
@@ -0,0 +1,443 @@
|
||||
package vlc
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/logger"
|
||||
"fmt"
|
||||
"github.com/AynaLivePlayer/miaosic"
|
||||
"github.com/adrg/libvlc-go/v3"
|
||||
"math"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var running bool = false
|
||||
var log logger.ILogger = nil
|
||||
var player *vlc.Player
|
||||
var eventManager *vlc.EventManager
|
||||
var lock sync.Mutex
|
||||
|
||||
// 状态变量
|
||||
var prevPercentPos float64 = 0
|
||||
var prevTimePos float64 = 0
|
||||
var duration float64 = 0
|
||||
var currentMedia model.Media
|
||||
var currentWindowHandle uintptr
|
||||
|
||||
var audioDevices []model.AudioDevice
|
||||
var currentAudioDevice string
|
||||
|
||||
var videoOptions = map[string][]string{
|
||||
"windows": {"--video-filter=adjust", "--directx-hwnd"},
|
||||
"darwin": {"--vout=macosx"},
|
||||
"linux": {"--vout=x11", "--x11-display=:0"},
|
||||
}
|
||||
|
||||
func setWindowHandle(handle uintptr) error {
|
||||
return nil
|
||||
os := runtime.GOOS
|
||||
switch os {
|
||||
case "windows":
|
||||
// Windows 平台使用 DirectX
|
||||
player.SetHWND(uintptr(handle))
|
||||
case "darwin":
|
||||
// macOS 平台使用 NSView
|
||||
player.SetNSObject(handle)
|
||||
case "linux":
|
||||
// Linux 平台使用 XWindow
|
||||
player.SetXWindow(uint32(handle))
|
||||
default:
|
||||
return fmt.Errorf("unsupported platform: %s", os)
|
||||
}
|
||||
|
||||
currentWindowHandle = handle
|
||||
|
||||
// 如果当前有媒体在播放,需要重新加载视频输出
|
||||
if player.IsPlaying() {
|
||||
player.Stop()
|
||||
player.Play()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetupPlayer() {
|
||||
running = true
|
||||
config.LoadConfig(cfg)
|
||||
log = global.Logger.WithPrefix("VLC Player")
|
||||
|
||||
opts := []string{"--no-video", "--quiet"}
|
||||
//os := runtime.GOOS
|
||||
//if platformOpts, ok := videoOptions[os]; ok {
|
||||
// opts = append(opts, platformOpts...)
|
||||
//}
|
||||
|
||||
// 初始化libvlc
|
||||
if err := vlc.Init(opts...); err != nil {
|
||||
log.Error("initialize libvlc failed: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 创建播放器
|
||||
var err error
|
||||
player, err = vlc.NewPlayer()
|
||||
if err != nil {
|
||||
log.Error("create player failed: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取事件管理器
|
||||
eventManager, err = player.EventManager()
|
||||
if err != nil {
|
||||
log.Error("get event manager failed: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 注册事件
|
||||
registerEvents()
|
||||
registerCmdHandler()
|
||||
updateAudioDeviceList()
|
||||
restoreConfig()
|
||||
log.Info("VLC player initialized")
|
||||
}
|
||||
|
||||
func StopPlayer() {
|
||||
log.Info("stopping VLC player")
|
||||
if currentAudioDevice != "" {
|
||||
cfg.AudioDevice = currentAudioDevice
|
||||
log.Infof("save audio device config: %s", cfg.AudioDevice)
|
||||
}
|
||||
running = false
|
||||
if player != nil {
|
||||
err := player.Stop()
|
||||
if err != nil {
|
||||
log.Error("stop player failed: ", err)
|
||||
}
|
||||
err = player.Release()
|
||||
if err != nil {
|
||||
log.Error("release player failed: ", err)
|
||||
}
|
||||
}
|
||||
err := vlc.Release()
|
||||
if err != nil {
|
||||
log.Error("release player failed: ", err)
|
||||
}
|
||||
log.Info("VLC player stopped")
|
||||
}
|
||||
|
||||
func registerEvents() {
|
||||
// 播放结束事件
|
||||
_, err := eventManager.Attach(vlc.MediaPlayerEndReached, func(e vlc.Event, userData interface{}) {
|
||||
global.EventManager.CallA(events.PlayerPropertyIdleActiveUpdate, events.PlayerPropertyIdleActiveUpdateEvent{
|
||||
IsIdle: true,
|
||||
})
|
||||
global.EventManager.CallA(events.PlayerPlayingUpdate, events.PlayerPlayingUpdateEvent{
|
||||
Media: model.Media{},
|
||||
Removed: true,
|
||||
})
|
||||
}, nil)
|
||||
if err != nil {
|
||||
log.Error("register MediaPlayerEndReached event failed: ", err)
|
||||
}
|
||||
|
||||
// 播放位置改变事件
|
||||
_, err = eventManager.Attach(vlc.MediaPlayerPositionChanged, func(e vlc.Event, userData interface{}) {
|
||||
pos32, _ := player.MediaPosition()
|
||||
pos := float64(pos32)
|
||||
if duration > 0 {
|
||||
timePos := pos * duration
|
||||
percentPos := pos * 100
|
||||
// 忽略小变化
|
||||
if math.Abs(timePos-prevTimePos) < 0.5 && math.Abs(percentPos-prevPercentPos) < 0.5 {
|
||||
return
|
||||
}
|
||||
prevTimePos = timePos
|
||||
prevPercentPos = percentPos
|
||||
global.EventManager.CallA(events.PlayerPropertyTimePosUpdate, events.PlayerPropertyTimePosUpdateEvent{
|
||||
TimePos: timePos,
|
||||
})
|
||||
global.EventManager.CallA(events.PlayerPropertyPercentPosUpdate, events.PlayerPropertyPercentPosUpdateEvent{
|
||||
PercentPos: percentPos,
|
||||
})
|
||||
}
|
||||
}, nil)
|
||||
if err != nil {
|
||||
log.Error("register MediaPlayerPositionChanged event failed: ", err)
|
||||
}
|
||||
|
||||
// 时间改变事件(获取时长)
|
||||
_, err = eventManager.Attach(vlc.MediaPlayerTimeChanged, func(e vlc.Event, userData interface{}) {
|
||||
dur, _ := player.MediaLength()
|
||||
duration = float64(dur) / 1000.0 // 转换为秒
|
||||
global.EventManager.CallA(events.PlayerPropertyDurationUpdate, events.PlayerPropertyDurationUpdateEvent{
|
||||
Duration: duration,
|
||||
})
|
||||
}, nil)
|
||||
if err != nil {
|
||||
log.Error("register MediaPlayerTimeChanged event failed: ", err)
|
||||
}
|
||||
|
||||
// 暂停状态改变
|
||||
_, err = eventManager.Attach(vlc.MediaPlayerPaused, func(e vlc.Event, userData interface{}) {
|
||||
log.Debug("VLC player paused")
|
||||
global.EventManager.CallA(events.PlayerPropertyPauseUpdate, events.PlayerPropertyPauseUpdateEvent{
|
||||
Paused: true,
|
||||
})
|
||||
}, nil)
|
||||
if err != nil {
|
||||
log.Error("register MediaPlayerPaused event failed: ", err)
|
||||
}
|
||||
|
||||
_, err = eventManager.Attach(vlc.MediaPlayerPlaying, func(e vlc.Event, userData interface{}) {
|
||||
log.Debug("VLC player playing")
|
||||
global.EventManager.CallA(events.PlayerPropertyPauseUpdate, events.PlayerPropertyPauseUpdateEvent{
|
||||
Paused: false,
|
||||
})
|
||||
}, nil)
|
||||
if err != nil {
|
||||
log.Error("register MediaPlayerPlaying event failed: ", err)
|
||||
}
|
||||
|
||||
_, err = eventManager.Attach(vlc.MediaPlayerAudioVolume, func(e vlc.Event, userData interface{}) {
|
||||
volume, _ := player.Volume()
|
||||
log.Debug("VLC player audio volume: ", volume)
|
||||
global.EventManager.CallA(events.PlayerPropertyVolumeUpdate, events.PlayerPropertyVolumeUpdateEvent{
|
||||
Volume: float64(volume),
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func registerCmdHandler() {
|
||||
global.EventManager.RegisterA(events.PlayerPlayCmd, "player.play", func(evnt *event.Event) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
mediaInfo := evnt.Data.(events.PlayerPlayCmdEvent).Media.Info
|
||||
log.Infof("[VLC Player] Play media %s", mediaInfo.Title)
|
||||
|
||||
mediaUrls, err := miaosic.GetMediaUrl(mediaInfo.Meta, miaosic.QualityAny)
|
||||
if err != nil || len(mediaUrls) == 0 {
|
||||
log.Warn("[VLC PlayControl] get media url failed ", mediaInfo.Meta.ID(), err)
|
||||
global.EventManager.CallA(
|
||||
events.PlayerPlayErrorUpdate,
|
||||
events.PlayerPlayErrorUpdateEvent{
|
||||
Error: err,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 创建媒体对象
|
||||
var media *vlc.Media
|
||||
if strings.HasPrefix(mediaUrls[0].Url, "http") {
|
||||
media, err = vlc.NewMediaFromURL(mediaUrls[0].Url)
|
||||
} else {
|
||||
media, err = vlc.NewMediaFromPath(mediaUrls[0].Url)
|
||||
}
|
||||
if err != nil {
|
||||
log.Error("create media failed: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 设置HTTP头
|
||||
if val, ok := mediaUrls[0].Header["User-Agent"]; ok {
|
||||
err = media.AddOptions(":http-user-agent=" + val)
|
||||
if err != nil {
|
||||
log.Warn("add http-user-agent options failed: ", err)
|
||||
}
|
||||
}
|
||||
if val, ok := mediaUrls[0].Header["Referer"]; ok {
|
||||
err = media.AddOptions(":http-referrer=" + val)
|
||||
if err != nil {
|
||||
log.Warn("add http-referrer options failed: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新媒体信息
|
||||
mediaData := evnt.Data.(events.PlayerPlayCmdEvent).Media
|
||||
if m, err := miaosic.GetMediaInfo(mediaData.Info.Meta); err == nil {
|
||||
mediaData.Info = m
|
||||
}
|
||||
currentMedia = mediaData
|
||||
|
||||
global.EventManager.CallA(events.PlayerPlayingUpdate, events.PlayerPlayingUpdateEvent{
|
||||
Media: mediaData,
|
||||
Removed: false,
|
||||
})
|
||||
|
||||
// 播放
|
||||
if err := player.SetMedia(media); err != nil {
|
||||
log.Error("set media failed: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
if currentWindowHandle != 0 {
|
||||
if err := setWindowHandle(currentWindowHandle); err != nil {
|
||||
log.Error("apply window handle failed: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := player.Play(); err != nil {
|
||||
log.Error("play failed: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 重置位置信息
|
||||
prevPercentPos = 0
|
||||
prevTimePos = 0
|
||||
global.EventManager.CallA(events.PlayerPropertyTimePosUpdate, events.PlayerPropertyTimePosUpdateEvent{
|
||||
TimePos: 0,
|
||||
})
|
||||
global.EventManager.CallA(events.PlayerPropertyPercentPosUpdate, events.PlayerPropertyPercentPosUpdateEvent{
|
||||
PercentPos: 0,
|
||||
})
|
||||
global.EventManager.CallA(events.PlayerPropertyIdleActiveUpdate, events.PlayerPropertyIdleActiveUpdateEvent{
|
||||
IsIdle: false,
|
||||
})
|
||||
})
|
||||
|
||||
global.EventManager.RegisterA(events.PlayerToggleCmd, "player.toggle", func(evnt *event.Event) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
err := player.TogglePause()
|
||||
if err != nil {
|
||||
log.Errorf("[VLC Player] Toggle pause failed: %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
global.EventManager.RegisterA(events.PlayerSetPauseCmd, "player.set_paused", func(evnt *event.Event) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
data := evnt.Data.(events.PlayerSetPauseCmdEvent)
|
||||
err := player.SetPause(data.Pause)
|
||||
if err != nil {
|
||||
log.Errorf("[VLC Player] SetPause failed: %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
global.EventManager.RegisterA(events.PlayerSeekCmd, "player.seek", func(evnt *event.Event) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
data := evnt.Data.(events.PlayerSeekCmdEvent)
|
||||
if data.Absolute {
|
||||
player.SetMediaTime(int(data.Position * 1000)) // 转换为毫秒
|
||||
} else {
|
||||
player.SetMediaPosition(float32(data.Position / 100))
|
||||
}
|
||||
})
|
||||
|
||||
global.EventManager.RegisterA(events.PlayerVolumeChangeCmd, "player.volume", func(evnt *event.Event) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
data := evnt.Data.(events.PlayerVolumeChangeCmdEvent)
|
||||
err := player.SetVolume(int(data.Volume))
|
||||
if err != nil {
|
||||
log.Errorf("[VLC Player] SetVolume failed: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
global.EventManager.RegisterA(events.PlayerVideoPlayerSetWindowHandleCmd, "player.set_window_handle", func(evnt *event.Event) {
|
||||
handle := evnt.Data.(events.PlayerVideoPlayerSetWindowHandleCmdEvent).Handle
|
||||
setWindowHandle(handle)
|
||||
})
|
||||
|
||||
global.EventManager.RegisterA(events.PlayerSetAudioDeviceCmd, "player.set_audio_device", func(evnt *event.Event) {
|
||||
device := evnt.Data.(events.PlayerSetAudioDeviceCmdEvent).Device
|
||||
if err := setAudioDevice(device); err != nil {
|
||||
log.Warn("set audio device failed", err)
|
||||
global.EventManager.CallA(
|
||||
events.ErrorUpdate,
|
||||
events.ErrorUpdateEvent{
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// setAudioDevice 设置音频输出设备
|
||||
func setAudioDevice(deviceID string) error {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
log.Infof("set audio device to: %s", deviceID)
|
||||
|
||||
// 验证设备是否在列表中
|
||||
found := false
|
||||
for _, dev := range audioDevices {
|
||||
if dev.Name == deviceID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("audio device not found: %s", deviceID)
|
||||
}
|
||||
|
||||
// 设置音频设备
|
||||
if err := player.SetAudioOutputDevice(deviceID, ""); err != nil {
|
||||
log.Error("set audio device failed: ", err)
|
||||
return err
|
||||
}
|
||||
|
||||
currentAudioDevice = deviceID
|
||||
|
||||
// 更新配置
|
||||
cfg.AudioDevice = deviceID
|
||||
|
||||
// 发送更新事件
|
||||
global.EventManager.CallA(events.PlayerAudioDeviceUpdate, events.PlayerAudioDeviceUpdateEvent{
|
||||
Current: currentAudioDevice,
|
||||
Devices: audioDevices,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateAudioDeviceList 获取并更新音频设备列表
|
||||
func updateAudioDeviceList() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
// 获取所有音频设备
|
||||
devices, err := player.AudioOutputDevices()
|
||||
if err != nil {
|
||||
log.Error("get audio device list failed: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前音频设备
|
||||
currentDevice, err := player.AudioOutputDevice()
|
||||
if err != nil {
|
||||
log.Warn("get current audio device failed: ", err)
|
||||
currentDevice = ""
|
||||
}
|
||||
log.Debugf("current audio device list: %s", devices)
|
||||
log.Debugf("current audio device: %s", currentDevice)
|
||||
|
||||
// 转换设备格式
|
||||
audioDevices = make([]model.AudioDevice, 0, len(devices))
|
||||
for _, device := range devices {
|
||||
audioDevices = append(audioDevices, model.AudioDevice{
|
||||
Name: device.Name,
|
||||
Description: device.Description,
|
||||
})
|
||||
}
|
||||
|
||||
currentAudioDevice = currentDevice
|
||||
|
||||
log.Infof("update audio device list: %d devices, current: %s",
|
||||
len(audioDevices), currentAudioDevice)
|
||||
|
||||
// 发送事件通知
|
||||
global.EventManager.CallA(events.PlayerAudioDeviceUpdate, events.PlayerAudioDeviceUpdateEvent{
|
||||
Current: currentAudioDevice,
|
||||
Devices: audioDevices,
|
||||
})
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
const (
|
||||
ProgramName = "卡西米尔唱片机"
|
||||
Version uint32 = 0x010103
|
||||
Version uint32 = 0x010200
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -2,7 +2,8 @@ package config
|
||||
|
||||
type _ExperimentalConfig struct {
|
||||
BaseConfig
|
||||
Headless bool
|
||||
Headless bool
|
||||
PlayerCore string
|
||||
}
|
||||
|
||||
func (c *_ExperimentalConfig) Name() string {
|
||||
@@ -10,5 +11,6 @@ func (c *_ExperimentalConfig) Name() string {
|
||||
}
|
||||
|
||||
var Experimental = &_ExperimentalConfig{
|
||||
Headless: false,
|
||||
Headless: false,
|
||||
PlayerCore: "mpv",
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ type _GeneralConfig struct {
|
||||
PlayNextOnFail bool
|
||||
UseSystemPlaylist bool
|
||||
FixedSize bool
|
||||
EnableSMC bool // enable system media control
|
||||
EnableSMC bool // enable system media control
|
||||
CustomFonts string // use custom fonts, under ./assets file
|
||||
|
||||
}
|
||||
|
||||
@@ -30,4 +31,5 @@ var General = &_GeneralConfig{
|
||||
UseSystemPlaylist: true,
|
||||
FixedSize: true,
|
||||
EnableSMC: true,
|
||||
CustomFonts: "",
|
||||
}
|
||||
|
||||
Submodule pkg/miaosic updated: 6a9b5d60c3...5f59badfe1
@@ -135,6 +135,7 @@ func (d *Diange) Enable() error {
|
||||
"plugin.diange.queue.update",
|
||||
func(event *event.Event) {
|
||||
d.currentQueueLength = len(event.Data.(events.PlaylistDetailUpdateEvent).Medias)
|
||||
d.log.Debugf("current queue length: %d", d.currentQueueLength)
|
||||
medias := event.Data.(events.PlaylistDetailUpdateEvent).Medias
|
||||
tmpUserCount := make(map[string]int)
|
||||
for _, media := range medias {
|
||||
@@ -144,8 +145,11 @@ func (d *Diange) Enable() error {
|
||||
}
|
||||
tmpUserCount[media.ToUser().Name]++
|
||||
}
|
||||
// clear user count
|
||||
d.userCount.Clear()
|
||||
for user, count := range tmpUserCount {
|
||||
d.userCount.Store(user, count)
|
||||
d.log.Debugf("user media count in player playlist %s: %d", user, count)
|
||||
}
|
||||
})
|
||||
global.EventManager.RegisterA(
|
||||
|
||||
10
todo.txt
10
todo.txt
@@ -1,21 +1,21 @@
|
||||
- long text wrap in list
|
||||
- went idle and insert new item race condition
|
||||
- @3 fix handler execution (maybe priority)
|
||||
- @4 list refresh
|
||||
- @5 delete optimization
|
||||
- race condition in RichText. i dont care fu
|
||||
|
||||
- 适配歌词服务器
|
||||
- 媒体源 - 歌单信息获取
|
||||
- mpris, SMTC
|
||||
- 歌词event发送全部歌词,前端处理不同版本
|
||||
- mpris
|
||||
- optimize local music
|
||||
- 从搜索里添加的歌不能被切
|
||||
- 随机歌单
|
||||
- 下一首为非版权歌时自动切歌不生效
|
||||
- 网易云电台节目
|
||||
- 自定义弹幕服务器
|
||||
|
||||
----
|
||||
|
||||
Finished
|
||||
- 2024.06.30 : 添加vlc核心,修复若干bug,gui框架更新,修复点歌限制为1时可能出现的无法点歌的问题
|
||||
- 2024.05.27 : 修复web弹幕获取到0个host的时候闪退的问题
|
||||
- 2024.03.27 : 添加b站合集新链接的格式
|
||||
- 2024.03.22 : 添加酷狗歌单新链接的格式
|
||||
|
||||
Reference in New Issue
Block a user