mirror of
https://github.com/AynaLivePlayer/AynaLivePlayer.git
synced 2025-12-06 10:22:50 +08:00
gui refactor
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui"
|
||||
"AynaLivePlayer/gui/gctx"
|
||||
"AynaLivePlayer/internal"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
@@ -41,7 +43,7 @@ var Log = &_LogConfig{
|
||||
|
||||
func setupGlobal() {
|
||||
//global.EventManager = event.NewManger(128, 16)
|
||||
global.EventBus = eventbus.New()
|
||||
global.EventBus = eventbus.New(eventbus.WithMaxWorkerSize(len(events.EventsMapping)))
|
||||
global.Logger = loggerRepo.NewZapColoredLogger(Log.Path, !*dev)
|
||||
global.Logger.SetLogLevel(Log.Level)
|
||||
}
|
||||
@@ -78,13 +80,13 @@ func main() {
|
||||
<-quit
|
||||
} else {
|
||||
gui.Initialize()
|
||||
gui.MainWindow.ShowAndRun()
|
||||
gctx.Context.Window.ShowAndRun()
|
||||
}
|
||||
global.Logger.Info("closing internal server")
|
||||
internal.Stop()
|
||||
global.Logger.Infof("closing event manager")
|
||||
//global.EventManager.Stop()
|
||||
_ = global.EventBus.Stop()
|
||||
_ = global.EventBus.Wait()
|
||||
if *dev {
|
||||
global.Logger.Infof("saving translation")
|
||||
i18n.SaveTranslation()
|
||||
|
||||
BIN
assets/msyh.ttc
BIN
assets/msyh.ttc
Binary file not shown.
BIN
assets/msyh0.ttf
BIN
assets/msyh0.ttf
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -50,7 +50,7 @@ type ErrorUpdateEvent struct {
|
||||
// Value model.PlayerPropertyValue
|
||||
//}
|
||||
//
|
||||
//type LiveRoomStatusUpdateEvent struct {
|
||||
//type UpdateLiveRoomStatusData struct {
|
||||
// RoomTitle string
|
||||
// Status bool
|
||||
//}
|
||||
|
||||
25
core/events/events.go
Normal file
25
core/events/events.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package events
|
||||
|
||||
/*
|
||||
# events package
|
||||
|
||||
events package contains all events used in application.
|
||||
|
||||
in theory. all interaction should use events package.
|
||||
|
||||
the events are dispatched using eventbus package.
|
||||
|
||||
Here are some major events
|
||||
|
||||
- cmd: call cmd
|
||||
- reply: call reply
|
||||
- update: information updating event. usually issued by internal controller and broadcast to all channel
|
||||
|
||||
|
||||
naming convention
|
||||
|
||||
- cmd: 'cmd.event.id.'
|
||||
- reply: 'reply.same.same.cmd.id'
|
||||
- update: 'update.event.id'
|
||||
|
||||
*/
|
||||
@@ -5,55 +5,56 @@ import (
|
||||
liveroomsdk "github.com/AynaLivePlayer/liveroom-sdk"
|
||||
)
|
||||
|
||||
const LiveRoomAddCmd = "cmd.liveroom.add"
|
||||
const CmdLiveRoomAdd = "cmd.liveroom.add"
|
||||
|
||||
type LiveRoomAddCmdEvent struct {
|
||||
type CmdLiveRoomAddData struct {
|
||||
Title string
|
||||
Provider string
|
||||
RoomKey string
|
||||
}
|
||||
|
||||
const CmdLiveRoomRemove = "cmd.liveroom.remove"
|
||||
|
||||
type CmdLiveRoomRemoveData struct {
|
||||
Identifier string
|
||||
}
|
||||
|
||||
const CmdLiveRoomConfigChange = "cmd.liveroom.config.change"
|
||||
|
||||
type CmdLiveRoomConfigChangeData struct {
|
||||
Identifier string
|
||||
Config model.LiveRoomConfig
|
||||
}
|
||||
|
||||
const LiveRoomProviderUpdate = "update.liveroom.provider"
|
||||
|
||||
type LiveRoomProviderUpdateEvent struct {
|
||||
Providers []model.LiveRoomProviderInfo
|
||||
}
|
||||
|
||||
const LiveRoomRemoveCmd = "cmd.liveroom.remove"
|
||||
const UpdateLiveRoomRooms = "update.liveroom.rooms"
|
||||
|
||||
type LiveRoomRemoveCmdEvent struct {
|
||||
Identifier string
|
||||
}
|
||||
|
||||
const LiveRoomRoomsUpdate = "update.liveroom.rooms"
|
||||
|
||||
type LiveRoomRoomsUpdateEvent struct {
|
||||
type UpdateLiveRoomRoomsData struct {
|
||||
Rooms []model.LiveRoom
|
||||
}
|
||||
|
||||
const LiveRoomStatusUpdate = "update.liveroom.status"
|
||||
const UpdateLiveRoomStatus = "update.liveroom.status"
|
||||
|
||||
type LiveRoomStatusUpdateEvent struct {
|
||||
type UpdateLiveRoomStatusData struct {
|
||||
Room model.LiveRoom
|
||||
}
|
||||
|
||||
const LiveRoomConfigChangeCmd = "cmd.liveroom.config.change"
|
||||
const CmdLiveRoomOperation = "cmd.liveroom.operation"
|
||||
|
||||
type LiveRoomConfigChangeCmdEvent struct {
|
||||
Identifier string
|
||||
Config model.LiveRoomConfig
|
||||
}
|
||||
|
||||
const LiveRoomOperationCmd = "cmd.liveroom.operation"
|
||||
|
||||
type LiveRoomOperationCmdEvent struct {
|
||||
type CmdLiveRoomOperationData struct {
|
||||
Identifier string
|
||||
SetConnect bool // connect or disconnect
|
||||
}
|
||||
|
||||
const LiveRoomOperationFinish = "update.liveroom.operation"
|
||||
const ReplyLiveRoomOperation = "reply.liveroom.operation"
|
||||
|
||||
type LiveRoomOperationFinishEvent struct {
|
||||
type ReplyLiveRoomOperationData struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
const LiveRoomMessageReceive = "update.liveroom.message"
|
||||
|
||||
@@ -8,13 +8,13 @@ import (
|
||||
)
|
||||
|
||||
var EventsMapping = map[string]any{
|
||||
LiveRoomAddCmd: LiveRoomAddCmdEvent{},
|
||||
CmdLiveRoomAdd: CmdLiveRoomAddData{},
|
||||
LiveRoomProviderUpdate: LiveRoomProviderUpdateEvent{},
|
||||
LiveRoomRemoveCmd: LiveRoomRemoveCmdEvent{},
|
||||
LiveRoomRoomsUpdate: LiveRoomRoomsUpdateEvent{},
|
||||
LiveRoomStatusUpdate: LiveRoomStatusUpdateEvent{},
|
||||
LiveRoomConfigChangeCmd: LiveRoomConfigChangeCmdEvent{},
|
||||
LiveRoomOperationCmd: LiveRoomOperationCmdEvent{},
|
||||
CmdLiveRoomRemove: CmdLiveRoomRemoveData{},
|
||||
UpdateLiveRoomRooms: UpdateLiveRoomRoomsData{},
|
||||
UpdateLiveRoomStatus: UpdateLiveRoomStatusData{},
|
||||
CmdLiveRoomConfigChange: CmdLiveRoomConfigChangeData{},
|
||||
CmdLiveRoomOperation: CmdLiveRoomOperationData{},
|
||||
PlayerVolumeChangeCmd: PlayerVolumeChangeCmdEvent{},
|
||||
PlayerPlayCmd: PlayerPlayCmdEvent{},
|
||||
PlayerPlayErrorUpdate: PlayerPlayErrorUpdateEvent{},
|
||||
@@ -44,13 +44,13 @@ var EventsMapping = map[string]any{
|
||||
PlaylistManagerAddPlaylistCmd: PlaylistManagerAddPlaylistCmdEvent{},
|
||||
PlaylistManagerRemovePlaylistCmd: PlaylistManagerRemovePlaylistCmdEvent{},
|
||||
MediaProviderUpdate: MediaProviderUpdateEvent{},
|
||||
SearchCmd: SearchCmdEvent{},
|
||||
SearchResultUpdate: SearchResultUpdateEvent{},
|
||||
CmdMiaosicSearch: CmdMiaosicSearchData{},
|
||||
ReplyMiaosicSearch: ReplyMiaosicSearchData{},
|
||||
GUISetPlayerWindowOpenCmd: GUISetPlayerWindowOpenCmdEvent{},
|
||||
}
|
||||
|
||||
func init() {
|
||||
for _, v := range []model.PlaylistID{model.PlaylistIDSystem, model.PlaylistIDPlayer, model.PlaylistIDHistory} {
|
||||
for _, v := range []model.PlaylistID{model.PlaylistIDSystem, model.PlaylistIDPlayer} {
|
||||
EventsMapping[PlaylistDetailUpdate(v)] = PlaylistDetailUpdateEvent{}
|
||||
EventsMapping[PlaylistMoveCmd(v)] = PlaylistMoveCmdEvent{}
|
||||
EventsMapping[PlaylistSetIndexCmd(v)] = PlaylistSetIndexCmdEvent{}
|
||||
|
||||
@@ -7,16 +7,16 @@ import (
|
||||
)
|
||||
|
||||
func TestUnmarshalEventData(t *testing.T) {
|
||||
eventData := LiveRoomAddCmdEvent{
|
||||
eventData := CmdLiveRoomAddData{
|
||||
Title: "test",
|
||||
Provider: "asdfasd",
|
||||
RoomKey: "asdfasdf",
|
||||
}
|
||||
data, err := json.Marshal(eventData)
|
||||
require.NoError(t, err)
|
||||
val, err := UnmarshalEventData(LiveRoomAddCmd, data)
|
||||
val, err := UnmarshalEventData(CmdLiveRoomAdd, data)
|
||||
require.NoError(t, err)
|
||||
resultData, ok := val.(LiveRoomAddCmdEvent)
|
||||
resultData, ok := val.(CmdLiveRoomAddData)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, eventData, resultData)
|
||||
}
|
||||
|
||||
30
core/events/miaosic.go
Normal file
30
core/events/miaosic.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package events
|
||||
|
||||
import "github.com/AynaLivePlayer/miaosic"
|
||||
|
||||
const CmdMiaosicGetMediaInfo = "cmd.miaosic.getMediaInfo"
|
||||
|
||||
type CmdMiaosicGetMediaInfoData struct {
|
||||
Meta miaosic.MetaData `json:"meta"`
|
||||
}
|
||||
|
||||
const ReplyMiaosicGetMediaInfo = "reply.miaosic.getMediaInfo"
|
||||
|
||||
type ReplyMiaosicGetMediaInfoData struct {
|
||||
Info miaosic.MediaInfo `json:"info"`
|
||||
Error error
|
||||
}
|
||||
|
||||
const CmdMiaosicGetMediaUrl = "cmd.miaosic.getMediaUrl"
|
||||
|
||||
type CmdMiaosicGetMediaUrlData struct {
|
||||
Meta miaosic.MetaData `json:"meta"`
|
||||
Quality miaosic.Quality `json:"quality"`
|
||||
}
|
||||
|
||||
const ReplyMiaosicGetMediaUrl = "reply.miaosic.getMediaUrl"
|
||||
|
||||
type ReplyMiaosicGetMediaUrlData struct {
|
||||
Urls []miaosic.MediaUrl `json:"urls"`
|
||||
Error error
|
||||
}
|
||||
@@ -4,15 +4,15 @@ import (
|
||||
"AynaLivePlayer/core/model"
|
||||
)
|
||||
|
||||
const SearchCmd = "cmd.search"
|
||||
const CmdMiaosicSearch = "cmd.search"
|
||||
|
||||
type SearchCmdEvent struct {
|
||||
type CmdMiaosicSearchData struct {
|
||||
Keyword string
|
||||
Provider string
|
||||
}
|
||||
|
||||
const SearchResultUpdate = "update.search_result"
|
||||
const ReplyMiaosicSearch = "update.search_result"
|
||||
|
||||
type SearchResultUpdateEvent struct {
|
||||
type ReplyMiaosicSearchData struct {
|
||||
Medias []model.Media
|
||||
}
|
||||
|
||||
@@ -13,9 +13,8 @@ const (
|
||||
type PlaylistID string
|
||||
|
||||
const (
|
||||
PlaylistIDPlayer PlaylistID = "player"
|
||||
PlaylistIDSystem PlaylistID = "system"
|
||||
PlaylistIDHistory PlaylistID = "history"
|
||||
PlaylistIDPlayer PlaylistID = "player"
|
||||
PlaylistIDSystem PlaylistID = "system"
|
||||
)
|
||||
|
||||
type PlaylistInfo struct {
|
||||
|
||||
3
go.mod
3
go.mod
@@ -8,7 +8,7 @@ replace (
|
||||
github.com/AynaLivePlayer/liveroom-sdk v0.1.0 => ./pkg/liveroom-sdk // submodule
|
||||
github.com/AynaLivePlayer/miaosic v0.2.3 => ./pkg/miaosic // submodule
|
||||
|
||||
github.com/saltosystems/winrt-go => github.com/go-musicfox/winrt-go v0.1.4 // winrt with media foundation
|
||||
github.com/saltosystems/winrt-go => github.com/AynaLivePlayer/winrt-go v0.0.0-20250902062117-902ba325aee0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -23,6 +23,7 @@ require (
|
||||
github.com/go-ole/go-ole v1.3.0
|
||||
github.com/go-resty/resty/v2 v2.16.5
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/k0kubun/pp/v3 v3.5.0
|
||||
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-20241223121953-98e32661f6ff
|
||||
|
||||
4
go.sum
4
go.sum
@@ -4,6 +4,8 @@ fyne.io/systray v1.11.1-0.20250603113521-ca66a66d8b58 h1:eA5/u2XRd8OUkoMqEv3IBlF
|
||||
fyne.io/systray v1.11.1-0.20250603113521-ca66a66d8b58/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
|
||||
github.com/AynaLivePlayer/blivedm-go v0.0.0-20250629154348-690af765bfbc h1:t1fMdqUjB2lR9uuGQ9yWJ7LJ3h1hXhI+LhbTpElPueI=
|
||||
github.com/AynaLivePlayer/blivedm-go v0.0.0-20250629154348-690af765bfbc/go.mod h1:u+JfexgX5pYrylIuC5zP3N/Ylp47K/xvl+ntpZtosuE=
|
||||
github.com/AynaLivePlayer/winrt-go v0.0.0-20250902062117-902ba325aee0 h1:orjVC4k6/CU7279G9abWaBIIiCgxUpDhkaM24o7arvs=
|
||||
github.com/AynaLivePlayer/winrt-go v0.0.0-20250902062117-902ba325aee0/go.mod h1:CIltaIm7qaANUIvzr0Vmz71lmQMAIbGJ7cvgzX7FMfA=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
@@ -56,8 +58,6 @@ github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw
|
||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 h1:RkGhqHxEVAvPM0/R+8g7XRwQnHatO0KAuVcwHo8q9W8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728/go.mod h1:SyRD8YfuKk+ZXlDqYiqe1qMSqjNgtHzBTG810KUagMc=
|
||||
github.com/go-musicfox/winrt-go v0.1.4 h1:xg+7VKsIozGK8S4X4zNQ/3HNhg5yHWYaTE+Zs4jySaU=
|
||||
github.com/go-musicfox/winrt-go v0.1.4/go.mod h1:CIltaIm7qaANUIvzr0Vmz71lmQMAIbGJ7cvgzX7FMfA=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package gui
|
||||
package component
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
40
gui/component/label.go
Normal file
40
gui/component/label.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type LabelOpt func(*widget.Label)
|
||||
|
||||
func LabelWrapping(wrapping fyne.TextWrap) LabelOpt {
|
||||
return func(l *widget.Label) {
|
||||
l.Wrapping = wrapping
|
||||
}
|
||||
}
|
||||
|
||||
func LabelAlignment(align fyne.TextAlign) LabelOpt {
|
||||
return func(l *widget.Label) {
|
||||
l.Alignment = align
|
||||
}
|
||||
}
|
||||
|
||||
func LabelTextStyle(style fyne.TextStyle) LabelOpt {
|
||||
return func(l *widget.Label) {
|
||||
l.TextStyle = style
|
||||
}
|
||||
}
|
||||
|
||||
func LabelTruncation(truncation fyne.TextTruncation) LabelOpt {
|
||||
return func(l *widget.Label) {
|
||||
l.Truncation = truncation
|
||||
}
|
||||
}
|
||||
|
||||
func NewLabelWithOpts(text string, opts ...LabelOpt) *widget.Label {
|
||||
l := widget.NewLabel(text)
|
||||
for _, opt := range opts {
|
||||
opt(l)
|
||||
}
|
||||
return l
|
||||
}
|
||||
28
gui/component/lyrics/LICENSE.txt
Normal file
28
gui/component/lyrics/LICENSE.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2024, Drew Weymouth
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
97
gui/component/lyrics/lyricline.go
Normal file
97
gui/component/lyrics/lyricline.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package lyrics
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/driver/desktop"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type lyricLine struct {
|
||||
widget.BaseWidget
|
||||
|
||||
Text string
|
||||
SizeName fyne.ThemeSizeName
|
||||
ColorName fyne.ThemeColorName
|
||||
HoveredColorName fyne.ThemeColorName
|
||||
Alignment fyne.TextAlign
|
||||
Tappable bool
|
||||
|
||||
onTapped func()
|
||||
hovered bool
|
||||
richtext *widget.RichText
|
||||
}
|
||||
|
||||
func newLyricLine(text string, onTapped func()) *lyricLine {
|
||||
l := &lyricLine{
|
||||
Text: text,
|
||||
SizeName: theme.SizeNameSubHeadingText,
|
||||
ColorName: theme.ColorNameForeground,
|
||||
Alignment: fyne.TextAlignLeading,
|
||||
onTapped: onTapped,
|
||||
}
|
||||
l.ExtendBaseWidget(l)
|
||||
return l
|
||||
}
|
||||
|
||||
var _ desktop.Hoverable = (*lyricLine)(nil)
|
||||
|
||||
func (l *lyricLine) MouseIn(*desktop.MouseEvent) {
|
||||
if l.Tappable {
|
||||
l.hovered = true
|
||||
l.Refresh()
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lyricLine) MouseMoved(*desktop.MouseEvent) {
|
||||
}
|
||||
|
||||
func (l *lyricLine) MouseOut() {
|
||||
l.hovered = false
|
||||
l.Refresh()
|
||||
}
|
||||
|
||||
var _ desktop.Cursorable = (*lyricLine)(nil)
|
||||
|
||||
func (l *lyricLine) Cursor() desktop.Cursor {
|
||||
if l.Tappable {
|
||||
return desktop.PointerCursor
|
||||
}
|
||||
return desktop.DefaultCursor
|
||||
}
|
||||
|
||||
var _ fyne.Tappable = (*lyricLine)(nil)
|
||||
|
||||
func (l *lyricLine) Tapped(*fyne.PointEvent) {
|
||||
if l.Tappable {
|
||||
l.onTapped()
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lyricLine) updateRichText() {
|
||||
if l.richtext == nil {
|
||||
l.richtext = widget.NewRichText(&widget.TextSegment{
|
||||
Style: widget.RichTextStyleSubHeading,
|
||||
})
|
||||
l.richtext.Wrapping = fyne.TextWrapWord
|
||||
}
|
||||
seg := l.richtext.Segments[0].(*widget.TextSegment)
|
||||
seg.Text = l.Text
|
||||
seg.Style.Alignment = l.Alignment
|
||||
if l.hovered {
|
||||
seg.Style.ColorName = l.HoveredColorName
|
||||
} else {
|
||||
seg.Style.ColorName = l.ColorName
|
||||
}
|
||||
seg.Style.SizeName = l.SizeName
|
||||
}
|
||||
|
||||
func (l *lyricLine) Refresh() {
|
||||
l.updateRichText()
|
||||
l.richtext.Refresh()
|
||||
}
|
||||
|
||||
func (l *lyricLine) CreateRenderer() fyne.WidgetRenderer {
|
||||
l.updateRichText()
|
||||
return widget.NewSimpleRenderer(l.richtext)
|
||||
}
|
||||
412
gui/component/lyrics/lyricsviewer.go
Normal file
412
gui/component/lyrics/lyricsviewer.go
Normal file
@@ -0,0 +1,412 @@
|
||||
package lyrics
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type ActiveLyricPosition int
|
||||
|
||||
const (
|
||||
// ActiveLyricPositionMiddle positions the active lyric line in the middle of the widget
|
||||
ActiveLyricPositionMiddle ActiveLyricPosition = iota
|
||||
|
||||
// ActiveLyricPositionUpperMiddle positions the active lyric line
|
||||
// in the upper-middle of the widget, roughly 1/3 of the way down
|
||||
ActiveLyricPositionUpperMiddle
|
||||
)
|
||||
|
||||
// LyricsViewer is a widget for displaying song lyrics.
|
||||
// It supports synced and unsynced mode. In synced mode, the active line
|
||||
// is highlighted and the widget can advance to the next line
|
||||
// with an animated scroll. In unsynced mode all lyrics are shown
|
||||
// in the active color and the user is allowed to scroll freely.
|
||||
type LyricsViewer struct {
|
||||
widget.BaseWidget
|
||||
|
||||
// Alignment controls the text alignment of the lyric lines
|
||||
Alignment fyne.TextAlign
|
||||
|
||||
// TextSizeName is the theme size name that controls the size of the lyric lines.
|
||||
// Defaults to theme.SizeNameSubHeadingText.
|
||||
TextSizeName fyne.ThemeSizeName
|
||||
|
||||
// ActiveLyricColorName is the theme color name that the currently active
|
||||
// lyric line will be drawn in synced mode, or all lyrics in non-synced mode.
|
||||
// Defaults to theme.ColorNameForeground.
|
||||
ActiveLyricColorName fyne.ThemeColorName
|
||||
|
||||
// InactiveLyricColorName is the theme color name that the inactive lyric lines
|
||||
// will be drawn in synced mode. Defaults to theme.ColorNameDisabled.
|
||||
InactiveLyricColorName fyne.ThemeColorName
|
||||
|
||||
// HoveredLyricColorName is the theme color name that hovered lyric lines
|
||||
// will be drawn in synced mode when an OnLyricTapped callback is set.
|
||||
// Defaults to theme.ColorNameHover.
|
||||
HoveredLyricColorName fyne.ThemeColorName
|
||||
|
||||
// ActiveLyricPosition sets the vertical positioning of the active lyric line
|
||||
// in synced mode.
|
||||
ActiveLyricPosition ActiveLyricPosition
|
||||
|
||||
// OnLyricTapped sets a callback function that is invoked when a
|
||||
// synced lyric line is tapped. The line number is *one-indexed*.
|
||||
// Typically used to seek to the timecode of the given lyric.
|
||||
// When showing unsynced lyrics, or if this callback is unset,
|
||||
// the visual styling of the widget will not indicate interactivity.
|
||||
OnLyricTapped func(lineNum int)
|
||||
|
||||
lines []string
|
||||
synced bool
|
||||
|
||||
// one-indexed - 0 means before the first line
|
||||
// during an animation, currentLine is the line
|
||||
// that will be scrolled when the animation is finished
|
||||
currentLine int
|
||||
|
||||
prototypeLyricLineSize fyne.Size
|
||||
|
||||
scroll *container.Scroll
|
||||
vbox *fyne.Container
|
||||
|
||||
// nil when an animation is not currently running
|
||||
anim *fyne.Animation
|
||||
animStartOffset float32
|
||||
}
|
||||
|
||||
// NewLyricsViewer returns a new lyrics viewer.
|
||||
func NewLyricsViewer() *LyricsViewer {
|
||||
s := &LyricsViewer{}
|
||||
s.ExtendBaseWidget(s)
|
||||
s.prototypeLyricLineSize = s.newLyricLine("Hello...", 0, false).MinSize()
|
||||
return s
|
||||
}
|
||||
|
||||
// SetLyrics sets the lyrics and also resets the current line to 0 if synced.
|
||||
func (l *LyricsViewer) SetLyrics(lines []string, synced bool) {
|
||||
l.lines = lines
|
||||
l.synced = synced
|
||||
l.currentLine = 0
|
||||
if l.scroll != nil {
|
||||
if synced {
|
||||
l.scroll.Direction = container.ScrollNone
|
||||
} else {
|
||||
l.scroll.Direction = container.ScrollVerticalOnly
|
||||
}
|
||||
}
|
||||
l.updateContent()
|
||||
}
|
||||
|
||||
// SetCurrentLine sets the current line that the lyric viewer is scrolled to.
|
||||
// Argument is *one-indexed* - SetCurrentLine(0) means setting the scroll to be
|
||||
// before the first line. In unsynced mode this is a no-op. This function is
|
||||
// typically called when the user has seeked the playing song to a new position.
|
||||
func (l *LyricsViewer) SetCurrentLine(line int) {
|
||||
if line < 0 || line > len(l.lines) {
|
||||
// do not panic, just ignore invalid input
|
||||
return
|
||||
}
|
||||
if l.vbox == nil || !l.synced {
|
||||
l.currentLine = line
|
||||
return // renderer not created yet or unsynced mode
|
||||
}
|
||||
inactiveColor := l.inactiveLyricColor()
|
||||
if l.checkStopAnimation() && l.currentLine > 1 {
|
||||
// we were in the middle of animation
|
||||
// make sure prev line is right color
|
||||
l.setLineColor(l.vbox.Objects[l.currentLine-1].(*lyricLine), inactiveColor, true)
|
||||
}
|
||||
if l.currentLine != 0 {
|
||||
l.setLineColor(l.vbox.Objects[l.currentLine].(*lyricLine), inactiveColor, true)
|
||||
}
|
||||
l.currentLine = line
|
||||
if l.currentLine != 0 {
|
||||
l.setLineColor(l.vbox.Objects[l.currentLine].(*lyricLine), l.activeLyricColor(), true)
|
||||
}
|
||||
l.scroll.Offset.Y = l.offsetForLine(l.currentLine)
|
||||
l.scroll.Refresh()
|
||||
}
|
||||
|
||||
// NextLine advances the lyric viewer to the next line with an animated scroll.
|
||||
// In unsynced mode this is a no-op.
|
||||
func (l *LyricsViewer) NextLine() {
|
||||
if l.vbox == nil || !l.synced {
|
||||
return // no renderer yet, or unsynced lyrics (no-op)
|
||||
}
|
||||
|
||||
if l.currentLine == len(l.lines) {
|
||||
return // already at last line
|
||||
}
|
||||
if l.checkStopAnimation() {
|
||||
// we were in the middle of animation - short-circuit it to completed
|
||||
// make sure prev and current lines are right color and scrolled to the end
|
||||
if l.currentLine > 1 {
|
||||
l.setLineColor(l.vbox.Objects[l.currentLine-1].(*lyricLine), l.inactiveLyricColor(), true)
|
||||
}
|
||||
l.setLineColor(l.vbox.Objects[l.currentLine].(*lyricLine), l.activeLyricColor(), true)
|
||||
l.scroll.Offset.Y = l.offsetForLine(l.currentLine)
|
||||
}
|
||||
l.currentLine++
|
||||
|
||||
var prevLine, nextLine *lyricLine
|
||||
if l.currentLine > 1 {
|
||||
prevLine = l.vbox.Objects[l.currentLine-1].(*lyricLine)
|
||||
}
|
||||
if l.currentLine <= len(l.lines) {
|
||||
nextLine = l.vbox.Objects[l.currentLine].(*lyricLine)
|
||||
}
|
||||
|
||||
l.setupScrollAnimation(prevLine, nextLine)
|
||||
l.anim.Start()
|
||||
}
|
||||
|
||||
func (l *LyricsViewer) Refresh() {
|
||||
l.updateContent()
|
||||
}
|
||||
|
||||
func (l *LyricsViewer) MinSize() fyne.Size {
|
||||
// overridden because NoScroll will have minSize encompass the full lyrics
|
||||
// note also that leaving this to the renderer MinSize, based on the
|
||||
// VBox with RichText lines inside Scroll, may lead to race conditions
|
||||
// (https://github.com/fyne-io/fyne/issues/4890)
|
||||
minHeight := l.prototypeLyricLineSize.Height*3 + theme.Padding()*2
|
||||
return fyne.NewSize(l.prototypeLyricLineSize.Width, minHeight)
|
||||
}
|
||||
|
||||
func (l *LyricsViewer) Resize(size fyne.Size) {
|
||||
l.updateSpacerSize(size)
|
||||
l.BaseWidget.Resize(size)
|
||||
if l.vbox == nil {
|
||||
return // renderer not created yet
|
||||
}
|
||||
if l.anim == nil {
|
||||
l.scroll.Offset = fyne.NewPos(0, l.offsetForLine(l.currentLine))
|
||||
l.scroll.Refresh()
|
||||
} else {
|
||||
// animation is running - update its reference scroll pos
|
||||
l.animStartOffset = l.offsetForLine(l.currentLine - 1)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LyricsViewer) updateSpacerSize(size fyne.Size) {
|
||||
if l.vbox == nil {
|
||||
return // renderer not created yet
|
||||
}
|
||||
|
||||
ht := size.Height / 2
|
||||
if l.ActiveLyricPosition == ActiveLyricPositionUpperMiddle {
|
||||
ht = size.Height / 3
|
||||
}
|
||||
|
||||
var topSpaceHeight, bottomSpaceHeight float32
|
||||
if l.synced {
|
||||
topSpaceHeight = ht + l.prototypeLyricLineSize.Height/2
|
||||
// end spacer only needs to be big enough - can't be too big
|
||||
// so use a very simple height calculation
|
||||
bottomSpaceHeight = size.Height
|
||||
}
|
||||
l.vbox.Objects[0].(*vSpace).Height = topSpaceHeight
|
||||
l.vbox.Objects[len(l.vbox.Objects)-1].(*vSpace).Height = bottomSpaceHeight
|
||||
}
|
||||
|
||||
func (l *LyricsViewer) updateContent() {
|
||||
if l.vbox == nil {
|
||||
return // renderer not created yet
|
||||
}
|
||||
l.checkStopAnimation()
|
||||
|
||||
lnObj := len(l.vbox.Objects)
|
||||
if lnObj == 0 {
|
||||
l.vbox.Objects = append(l.vbox.Objects, NewVSpace(0), NewVSpace(0))
|
||||
lnObj = 2
|
||||
}
|
||||
l.updateSpacerSize(l.Size())
|
||||
endSpacer := l.vbox.Objects[lnObj-1]
|
||||
for i, line := range l.lines {
|
||||
lineNum := i + 1 // one-indexed
|
||||
useActiveColor := !l.synced || l.currentLine == lineNum
|
||||
if lineNum < lnObj-1 {
|
||||
rt := l.vbox.Objects[lineNum].(*lyricLine)
|
||||
if useActiveColor {
|
||||
l.setLineColor(rt, l.activeLyricColor(), false)
|
||||
} else {
|
||||
l.setLineColor(rt, l.inactiveLyricColor(), false)
|
||||
}
|
||||
l.setLineTextAndProperties(rt, line, lineNum, true)
|
||||
} else if lineNum < lnObj {
|
||||
// replacing end spacer (last element in Objects) with a new richtext
|
||||
l.vbox.Objects[lineNum] = l.newLyricLine(line, lineNum, useActiveColor)
|
||||
} else {
|
||||
// extending the Objects slice
|
||||
l.vbox.Objects = append(l.vbox.Objects, l.newLyricLine(line, lineNum, useActiveColor))
|
||||
}
|
||||
}
|
||||
for i := len(l.lines) + 1; i < lnObj; i++ {
|
||||
l.vbox.Objects[i] = nil
|
||||
}
|
||||
l.vbox.Objects = l.vbox.Objects[:len(l.lines)+1]
|
||||
l.vbox.Objects = append(l.vbox.Objects, endSpacer)
|
||||
l.vbox.Refresh()
|
||||
l.scroll.Offset.Y = l.offsetForLine(l.currentLine)
|
||||
l.scroll.Refresh()
|
||||
}
|
||||
|
||||
func (l *LyricsViewer) setupScrollAnimation(currentLine, nextLine *lyricLine) {
|
||||
// calculate total scroll distance for the animation
|
||||
scrollDist := theme.Padding()
|
||||
if currentLine != nil {
|
||||
scrollDist += currentLine.Size().Height / 2
|
||||
} else {
|
||||
scrollDist += l.prototypeLyricLineSize.Height / 2
|
||||
}
|
||||
if nextLine != nil {
|
||||
scrollDist += nextLine.Size().Height / 2
|
||||
} else {
|
||||
scrollDist += l.prototypeLyricLineSize.Height / 2
|
||||
}
|
||||
|
||||
l.animStartOffset = l.scroll.Offset.Y
|
||||
var alreadyUpdated bool
|
||||
l.anim = fyne.NewAnimation(140*time.Millisecond, func(f float32) {
|
||||
l.scroll.Offset.Y = l.animStartOffset + f*scrollDist
|
||||
l.scroll.Refresh()
|
||||
if !alreadyUpdated && f >= 0.5 {
|
||||
if nextLine != nil {
|
||||
l.setLineColor(nextLine, l.activeLyricColor(), true)
|
||||
}
|
||||
if currentLine != nil {
|
||||
l.setLineColor(currentLine, l.inactiveLyricColor(), true)
|
||||
}
|
||||
alreadyUpdated = true
|
||||
}
|
||||
if f == 1 /*end of animation*/ {
|
||||
l.anim = nil
|
||||
}
|
||||
})
|
||||
l.anim.Curve = fyne.AnimationEaseInOut
|
||||
}
|
||||
|
||||
func (l *LyricsViewer) offsetForLine(lineNum int /*one-indexed*/) float32 {
|
||||
if lineNum == 0 {
|
||||
return 0
|
||||
}
|
||||
pad := theme.Padding()
|
||||
offset := pad + l.prototypeLyricLineSize.Height/2
|
||||
for i := 1; i <= lineNum; i++ {
|
||||
if i > 1 {
|
||||
offset += l.vbox.Objects[i-1].MinSize().Height/2 + pad
|
||||
}
|
||||
offset += l.vbox.Objects[i].MinSize().Height / 2
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
func (l *LyricsViewer) newLyricLine(text string, lineNum int, useActiveColor bool) *lyricLine {
|
||||
ll := newLyricLine(text, nil)
|
||||
l.setLineTextAndProperties(ll, text, lineNum, false)
|
||||
ll.HoveredColorName = l.hoveredLyricColor()
|
||||
if useActiveColor {
|
||||
ll.ColorName = l.activeLyricColor()
|
||||
} else {
|
||||
ll.ColorName = l.inactiveLyricColor()
|
||||
}
|
||||
|
||||
return ll
|
||||
}
|
||||
|
||||
func (l *LyricsViewer) setLineTextAndProperties(ll *lyricLine, text string, lineNum int, refresh bool) {
|
||||
ll.Text = text
|
||||
ll.SizeName = l.textSizeName()
|
||||
ll.Alignment = l.Alignment
|
||||
ll.Tappable = l.synced && l.OnLyricTapped != nil
|
||||
ll.onTapped = func() {
|
||||
if l.OnLyricTapped != nil {
|
||||
l.OnLyricTapped(lineNum)
|
||||
}
|
||||
}
|
||||
if refresh {
|
||||
ll.Refresh()
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LyricsViewer) setLineColor(ll *lyricLine, colorName fyne.ThemeColorName, refresh bool) {
|
||||
ll.ColorName = colorName
|
||||
ll.HoveredColorName = l.hoveredLyricColor()
|
||||
if refresh {
|
||||
ll.Refresh()
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LyricsViewer) activeLyricColor() fyne.ThemeColorName {
|
||||
if l.ActiveLyricColorName != "" {
|
||||
return l.ActiveLyricColorName
|
||||
}
|
||||
return theme.ColorNameForeground
|
||||
}
|
||||
|
||||
func (l *LyricsViewer) inactiveLyricColor() fyne.ThemeColorName {
|
||||
if l.InactiveLyricColorName != "" {
|
||||
return l.InactiveLyricColorName
|
||||
}
|
||||
return theme.ColorNameDisabled
|
||||
}
|
||||
|
||||
func (l *LyricsViewer) hoveredLyricColor() fyne.ThemeColorName {
|
||||
if l.HoveredLyricColorName != "" {
|
||||
return l.HoveredLyricColorName
|
||||
}
|
||||
return theme.ColorNameHyperlink
|
||||
}
|
||||
|
||||
func (l *LyricsViewer) textSizeName() fyne.ThemeSizeName {
|
||||
if l.TextSizeName != "" {
|
||||
return l.TextSizeName
|
||||
}
|
||||
return theme.SizeNameSubHeadingText
|
||||
}
|
||||
|
||||
func (l *LyricsViewer) checkStopAnimation() bool {
|
||||
if l.anim != nil {
|
||||
l.anim.Stop()
|
||||
l.anim = nil
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (l *LyricsViewer) CreateRenderer() fyne.WidgetRenderer {
|
||||
l.vbox = container.NewVBox()
|
||||
l.scroll = container.NewScroll(l.vbox)
|
||||
if l.synced {
|
||||
l.scroll.Direction = container.ScrollNone
|
||||
} else {
|
||||
l.scroll.Direction = container.ScrollVerticalOnly
|
||||
}
|
||||
l.updateContent()
|
||||
return widget.NewSimpleRenderer(l.scroll)
|
||||
}
|
||||
|
||||
type vSpace struct {
|
||||
widget.BaseWidget
|
||||
|
||||
Height float32
|
||||
}
|
||||
|
||||
func NewVSpace(height float32) *vSpace {
|
||||
v := &vSpace{Height: height}
|
||||
v.ExtendBaseWidget(v)
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *vSpace) MinSize() fyne.Size {
|
||||
return fyne.NewSize(0, v.Height)
|
||||
}
|
||||
|
||||
func (v *vSpace) CreateRenderer() fyne.WidgetRenderer {
|
||||
return widget.NewSimpleRenderer(layout.NewSpacer())
|
||||
}
|
||||
3
gui/component/lyrics/readme.txt
Normal file
3
gui/component/lyrics/readme.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
license under bsd-3-clause
|
||||
|
||||
https://github.com/supersonic-app/fyne-lyrics
|
||||
@@ -1,5 +0,0 @@
|
||||
package gui
|
||||
|
||||
const (
|
||||
eventChannel = "gui"
|
||||
)
|
||||
43
gui/gctx/context.go
Normal file
43
gui/gctx/context.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package gctx
|
||||
|
||||
import (
|
||||
_logger "AynaLivePlayer/pkg/logger"
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
// gui context
|
||||
|
||||
const (
|
||||
EventChannel = "gui"
|
||||
)
|
||||
|
||||
var Logger _logger.ILogger = nil
|
||||
var Context *GuiContext = nil
|
||||
|
||||
type GuiContext struct {
|
||||
App fyne.App // application
|
||||
Window fyne.Window // main window
|
||||
EventChannel string
|
||||
onMainWindowClosing []func()
|
||||
}
|
||||
|
||||
func NewGuiContext(app fyne.App, mainWindow fyne.Window) *GuiContext {
|
||||
return &GuiContext{
|
||||
App: app,
|
||||
Window: mainWindow,
|
||||
EventChannel: EventChannel,
|
||||
onMainWindowClosing: make([]func(), 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *GuiContext) Init() {
|
||||
c.Window.SetOnClosed(func() {
|
||||
for _, f := range c.onMainWindowClosing {
|
||||
f()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (c *GuiContext) OnMainWindowClosing(f func()) {
|
||||
c.onMainWindowClosing = append(c.onMainWindowClosing, f)
|
||||
}
|
||||
78
gui/gui.go
78
gui/gui.go
@@ -3,7 +3,16 @@ package gui
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/gctx"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
configView "AynaLivePlayer/gui/views/config"
|
||||
"AynaLivePlayer/gui/views/history"
|
||||
"AynaLivePlayer/gui/views/liverooms"
|
||||
"AynaLivePlayer/gui/views/player"
|
||||
"AynaLivePlayer/gui/views/playlists"
|
||||
"AynaLivePlayer/gui/views/search"
|
||||
"AynaLivePlayer/gui/views/systray"
|
||||
"AynaLivePlayer/gui/views/updater"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
@@ -18,11 +27,6 @@ import (
|
||||
_logger "AynaLivePlayer/pkg/logger"
|
||||
)
|
||||
|
||||
var App fyne.App
|
||||
var MainWindow fyne.Window
|
||||
var playerWindow fyne.Window
|
||||
var playerWindowHandle uintptr
|
||||
|
||||
var logger _logger.ILogger = nil
|
||||
|
||||
func black_magic() {
|
||||
@@ -31,48 +35,48 @@ func black_magic() {
|
||||
|
||||
func Initialize() {
|
||||
logger = global.Logger.WithPrefix("GUI")
|
||||
gctx.Logger = logger
|
||||
black_magic()
|
||||
logger.Info("Initializing GUI")
|
||||
|
||||
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(getAppTitle())
|
||||
|
||||
mainApp := app.NewWithID(config.ProgramName)
|
||||
MainWindow := mainApp.NewWindow(getAppTitle())
|
||||
|
||||
gctx.Context = gctx.NewGuiContext(mainApp, MainWindow)
|
||||
gctx.Context.Init()
|
||||
|
||||
gctx.Context.OnMainWindowClosing(func() {
|
||||
_ = config.SaveToConfigFile(config.ConfigPath)
|
||||
logger.Infof("config saved to %s", config.ConfigPath)
|
||||
})
|
||||
|
||||
updater.CreateUpdaterPopUp()
|
||||
|
||||
tabs := container.NewAppTabs(
|
||||
container.NewTabItem(i18n.T("gui.tab.player"),
|
||||
container.NewBorder(nil, createPlayControllerV2(), nil, nil, createPlaylist()),
|
||||
),
|
||||
container.NewTabItem(i18n.T("gui.tab.search"),
|
||||
container.NewBorder(createSearchBar(), nil, nil, nil, createSearchList()),
|
||||
),
|
||||
container.NewTabItem(i18n.T("gui.tab.room"),
|
||||
container.NewBorder(nil, nil, createRoomSelector(), nil, createRoomController()),
|
||||
),
|
||||
container.NewTabItem(i18n.T("gui.tab.playlist"),
|
||||
container.NewBorder(nil, nil, createPlaylists(), nil, createPlaylistMedias()),
|
||||
),
|
||||
container.NewTabItem(i18n.T("gui.tab.history"),
|
||||
container.NewBorder(nil, nil, nil, nil, createHistoryList()),
|
||||
),
|
||||
container.NewTabItem(i18n.T("gui.tab.config"),
|
||||
createConfigLayout(),
|
||||
),
|
||||
container.NewTabItem(i18n.T("gui.tab.player"), player.CreateView()),
|
||||
container.NewTabItem(i18n.T("gui.tab.search"), search.CreateView()),
|
||||
container.NewTabItem(i18n.T("gui.tab.room"), liverooms.CreateView()),
|
||||
container.NewTabItem(i18n.T("gui.tab.playlist"), playlists.CreateView()),
|
||||
container.NewTabItem(i18n.T("gui.tab.history"), history.CreateView()),
|
||||
container.NewTabItem(i18n.T("gui.tab.config"), configView.CreateView()),
|
||||
)
|
||||
|
||||
tabs.SetTabLocation(container.TabLocationTop)
|
||||
MainWindow.SetIcon(resource.ImageIcon)
|
||||
MainWindow.SetContent(tabs)
|
||||
//MainWindow.Resize(fyne.NewSize(1280, 720))
|
||||
MainWindow.Resize(fyne.NewSize(config.General.Width, config.General.Height))
|
||||
MainWindow.SetFixedSize(config.General.FixedSize)
|
||||
|
||||
// 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.EventBus.Subscribe(eventChannel,
|
||||
global.EventBus.Subscribe(gctx.EventChannel,
|
||||
events.ErrorUpdate, "gui.show_error", gutil.ThreadSafeHandler(func(e *eventbus.Event) {
|
||||
err := e.Data.(events.ErrorUpdateEvent).Error
|
||||
logger.Warnf("gui received error event: %v, %v", err, err == nil)
|
||||
@@ -82,23 +86,7 @@ func Initialize() {
|
||||
dialog.ShowError(err, MainWindow)
|
||||
}))
|
||||
|
||||
checkUpdate()
|
||||
MainWindow.SetFixedSize(config.General.FixedSize)
|
||||
if config.General.ShowSystemTray {
|
||||
setupSysTray()
|
||||
} else {
|
||||
MainWindow.SetCloseIntercept(
|
||||
func() {
|
||||
// todo: save twice i don;t care
|
||||
_ = config.SaveToConfigFile(config.ConfigPath)
|
||||
MainWindow.Close()
|
||||
})
|
||||
systray.SetupSysTray()
|
||||
}
|
||||
MainWindow.SetOnClosed(func() {
|
||||
logger.Infof("GUI closing")
|
||||
if playerWindow != nil {
|
||||
logger.Infof("player window closing")
|
||||
playerWindow.Close()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
)
|
||||
|
||||
func GetWindowHandle(window fyne.Window) uintptr {
|
||||
// macos doesn't support --wid. :(
|
||||
return 0
|
||||
glfwWindow := getGlfwWindow(window)
|
||||
if glfwWindow == nil {
|
||||
return 0
|
||||
|
||||
249
gui/liverooms.go
249
gui/liverooms.go
@@ -1,249 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var RoomTab = &struct {
|
||||
Rooms *widget.List
|
||||
Index int
|
||||
AddBtn *widget.Button
|
||||
RemoveBtn *widget.Button
|
||||
RoomTitle *widget.Label
|
||||
RoomID *widget.Label
|
||||
Status *widget.Label
|
||||
AutoConnect *widget.Check
|
||||
ConnectBtn *widget.Button
|
||||
DisConnectBtn *widget.Button
|
||||
providers []model.LiveRoomProviderInfo
|
||||
rooms []model.LiveRoom
|
||||
lock sync.RWMutex
|
||||
}{}
|
||||
|
||||
func createRoomSelector() fyne.CanvasObject {
|
||||
RoomTab.Rooms = widget.NewList(
|
||||
func() int {
|
||||
return len(RoomTab.rooms)
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
return widget.NewLabel("AAAAAAAAAAAAAAAA")
|
||||
},
|
||||
func(id widget.ListItemID, object fyne.CanvasObject) {
|
||||
object.(*widget.Label).SetText(
|
||||
RoomTab.rooms[id].DisplayName())
|
||||
})
|
||||
RoomTab.AddBtn = widget.NewButton(i18n.T("gui.room.button.add"), func() {
|
||||
providerNames := make([]string, len(RoomTab.providers))
|
||||
for i := 0; i < len(RoomTab.providers); i++ {
|
||||
providerNames[i] = RoomTab.providers[i].Name
|
||||
}
|
||||
descriptionLabel := widget.NewLabel(i18n.T("gui.room.add.prompt"))
|
||||
clientNameEntry := widget.NewSelect(providerNames, func(s string) {
|
||||
for i := 0; i < len(RoomTab.providers); i++ {
|
||||
if RoomTab.providers[i].Name == s {
|
||||
descriptionLabel.SetText(i18n.T(RoomTab.providers[i].Description))
|
||||
break
|
||||
}
|
||||
descriptionLabel.SetText("")
|
||||
}
|
||||
})
|
||||
idEntry := widget.NewEntry()
|
||||
nameEntry := widget.NewEntry()
|
||||
dia := dialog.NewCustomConfirm(
|
||||
i18n.T("gui.room.add.title"),
|
||||
i18n.T("gui.room.add.confirm"),
|
||||
i18n.T("gui.room.add.cancel"),
|
||||
container.NewVBox(
|
||||
container.New(
|
||||
layout.NewFormLayout(),
|
||||
widget.NewLabel(i18n.T("gui.room.add.name")),
|
||||
nameEntry,
|
||||
widget.NewLabel(i18n.T("gui.room.add.client_name")),
|
||||
clientNameEntry,
|
||||
widget.NewLabel(i18n.T("gui.room.add.id_url")),
|
||||
idEntry,
|
||||
),
|
||||
descriptionLabel,
|
||||
),
|
||||
func(b bool) {
|
||||
if b && len(clientNameEntry.Selected) > 0 && len(idEntry.Text) > 0 {
|
||||
logger.Infof("Add room %s %s", clientNameEntry.Selected, idEntry.Text)
|
||||
_ = global.EventBus.PublishToChannel(eventChannel,
|
||||
events.LiveRoomAddCmd,
|
||||
events.LiveRoomAddCmdEvent{
|
||||
Title: nameEntry.Text,
|
||||
Provider: clientNameEntry.Selected,
|
||||
RoomKey: idEntry.Text,
|
||||
})
|
||||
}
|
||||
},
|
||||
MainWindow,
|
||||
)
|
||||
dia.Resize(fyne.NewSize(512, 256))
|
||||
dia.Show()
|
||||
})
|
||||
RoomTab.RemoveBtn = widget.NewButton(i18n.T("gui.room.button.remove"), func() {
|
||||
if len(RoomTab.rooms) == 0 {
|
||||
return
|
||||
}
|
||||
_ = global.EventBus.PublishToChannel(eventChannel,
|
||||
events.LiveRoomRemoveCmd,
|
||||
events.LiveRoomRemoveCmdEvent{
|
||||
Identifier: RoomTab.rooms[RoomTab.Index].LiveRoom.Identifier(),
|
||||
})
|
||||
})
|
||||
RoomTab.Rooms.OnSelected = func(id widget.ListItemID) {
|
||||
if id >= len(RoomTab.rooms) {
|
||||
return
|
||||
}
|
||||
logger.Infof("Select room %s", RoomTab.rooms[id].LiveRoom.Identifier())
|
||||
RoomTab.Index = id
|
||||
room := RoomTab.rooms[RoomTab.Index]
|
||||
RoomTab.RoomTitle.SetText(room.DisplayName())
|
||||
RoomTab.RoomID.SetText(room.LiveRoom.Identifier())
|
||||
RoomTab.AutoConnect.SetChecked(room.Config.AutoConnect)
|
||||
if room.Status {
|
||||
RoomTab.Status.SetText(i18n.T("gui.room.status.connected"))
|
||||
} else {
|
||||
RoomTab.Status.SetText(i18n.T("gui.room.status.disconnected"))
|
||||
}
|
||||
RoomTab.Status.Refresh()
|
||||
}
|
||||
registerRoomHandlers()
|
||||
return container.NewHBox(
|
||||
container.NewBorder(
|
||||
nil, container.NewCenter(container.NewHBox(RoomTab.AddBtn, RoomTab.RemoveBtn)),
|
||||
nil, nil,
|
||||
RoomTab.Rooms,
|
||||
),
|
||||
widget.NewSeparator(),
|
||||
)
|
||||
}
|
||||
|
||||
func registerRoomHandlers() {
|
||||
global.EventBus.Subscribe(eventChannel,
|
||||
events.LiveRoomProviderUpdate,
|
||||
"gui.liveroom.provider_update",
|
||||
gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
RoomTab.providers = event.Data.(events.LiveRoomProviderUpdateEvent).Providers
|
||||
RoomTab.Rooms.Refresh()
|
||||
}))
|
||||
global.EventBus.Subscribe(eventChannel,
|
||||
events.LiveRoomRoomsUpdate,
|
||||
"gui.liveroom.rooms_update",
|
||||
gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
logger.Infof("Update rooms")
|
||||
data := event.Data.(events.LiveRoomRoomsUpdateEvent)
|
||||
RoomTab.lock.Lock()
|
||||
RoomTab.rooms = data.Rooms
|
||||
RoomTab.Rooms.Select(0)
|
||||
RoomTab.Rooms.Refresh()
|
||||
RoomTab.lock.Unlock()
|
||||
}))
|
||||
global.EventBus.Subscribe(eventChannel,
|
||||
events.LiveRoomStatusUpdate,
|
||||
"gui.liveroom.room_status_update",
|
||||
gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
room := event.Data.(events.LiveRoomStatusUpdateEvent).Room
|
||||
index := -1
|
||||
for i := 0; i < len(RoomTab.rooms); i++ {
|
||||
if RoomTab.rooms[i].LiveRoom.Identifier() == room.LiveRoom.Identifier() {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if index == -1 {
|
||||
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())
|
||||
RoomTab.AutoConnect.SetChecked(room.Config.AutoConnect)
|
||||
if room.Status {
|
||||
RoomTab.Status.SetText(i18n.T("gui.room.status.connected"))
|
||||
} else {
|
||||
RoomTab.Status.SetText(i18n.T("gui.room.status.disconnected"))
|
||||
}
|
||||
RoomTab.Status.Refresh()
|
||||
}
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
func createRoomController() fyne.CanvasObject {
|
||||
RoomTab.ConnectBtn = widget.NewButton(i18n.T("gui.room.btn.connect"), func() {
|
||||
if RoomTab.Index >= len(RoomTab.rooms) {
|
||||
return
|
||||
}
|
||||
RoomTab.ConnectBtn.Disable()
|
||||
logger.Infof("Connect to room %s", RoomTab.rooms[RoomTab.Index].LiveRoom.Identifier())
|
||||
_ = global.EventBus.PublishToChannel(eventChannel,
|
||||
events.LiveRoomOperationCmd,
|
||||
events.LiveRoomOperationCmdEvent{
|
||||
Identifier: RoomTab.rooms[RoomTab.Index].LiveRoom.Identifier(),
|
||||
SetConnect: true,
|
||||
})
|
||||
})
|
||||
RoomTab.DisConnectBtn = widget.NewButton(i18n.T("gui.room.btn.disconnect"), func() {
|
||||
if RoomTab.Index >= len(RoomTab.rooms) {
|
||||
return
|
||||
}
|
||||
RoomTab.DisConnectBtn.Disable()
|
||||
logger.Infof("Disconnect from room %s", RoomTab.rooms[RoomTab.Index].LiveRoom.Identifier())
|
||||
_ = global.EventBus.PublishToChannel(eventChannel,
|
||||
events.LiveRoomOperationCmd,
|
||||
events.LiveRoomOperationCmdEvent{
|
||||
Identifier: RoomTab.rooms[RoomTab.Index].LiveRoom.Identifier(),
|
||||
SetConnect: false,
|
||||
})
|
||||
})
|
||||
global.EventBus.Subscribe(eventChannel,
|
||||
events.LiveRoomOperationFinish,
|
||||
"gui.liveroom.operation_finish",
|
||||
gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
RoomTab.ConnectBtn.Enable()
|
||||
RoomTab.DisConnectBtn.Enable()
|
||||
}))
|
||||
RoomTab.Status = widget.NewLabel(i18n.T("gui.room.waiting"))
|
||||
RoomTab.RoomTitle = widget.NewLabel("")
|
||||
RoomTab.RoomID = widget.NewLabel("")
|
||||
RoomTab.AutoConnect = widget.NewCheck(i18n.T("gui.room.check.autoconnect"), func(b bool) {
|
||||
if RoomTab.Index >= len(RoomTab.rooms) {
|
||||
return
|
||||
}
|
||||
logger.Infof("Change room %s autoconnect to %v", RoomTab.rooms[RoomTab.Index].LiveRoom.Identifier(), b)
|
||||
_ = global.EventBus.PublishToChannel(eventChannel,
|
||||
events.LiveRoomConfigChangeCmd,
|
||||
events.LiveRoomConfigChangeCmdEvent{
|
||||
Identifier: RoomTab.rooms[RoomTab.Index].LiveRoom.Identifier(),
|
||||
Config: model.LiveRoomConfig{
|
||||
AutoConnect: b,
|
||||
},
|
||||
})
|
||||
return
|
||||
})
|
||||
RoomTab.Rooms.Select(0)
|
||||
return container.NewVBox(
|
||||
RoomTab.RoomTitle,
|
||||
RoomTab.RoomID,
|
||||
RoomTab.Status,
|
||||
container.NewHBox(widget.NewLabel(i18n.T("gui.room.check.autoconnect")), RoomTab.AutoConnect),
|
||||
container.NewHBox(RoomTab.ConnectBtn, RoomTab.DisConnectBtn),
|
||||
)
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"github.com/AynaLivePlayer/miaosic"
|
||||
)
|
||||
|
||||
func createLyricObj(lyric *miaosic.Lyrics) []fyne.CanvasObject {
|
||||
lrcs := make([]fyne.CanvasObject, len(lyric.Content))
|
||||
for i := 0; i < len(lrcs); i++ {
|
||||
lr := widget.NewLabelWithStyle(
|
||||
lyric.Content[i].Lyric,
|
||||
fyne.TextAlignCenter, fyne.TextStyle{Italic: true})
|
||||
//lr.Wrapping = fyne.TextWrapWord
|
||||
// todo fix fyne bug
|
||||
lr.Wrapping = fyne.TextWrapBreak
|
||||
lrcs[i] = lr
|
||||
}
|
||||
return lrcs
|
||||
}
|
||||
|
||||
func createLyricWindow() fyne.Window {
|
||||
// create widgets
|
||||
w := App.NewWindow(i18n.T("gui.lyric.title"))
|
||||
currentLrc := newLabelWithWrapping("", fyne.TextWrapBreak)
|
||||
currentLrc.Alignment = fyne.TextAlignCenter
|
||||
fullLrc := container.NewVBox()
|
||||
lrcWindow := container.NewVScroll(fullLrc)
|
||||
prevIndex := 0
|
||||
w.SetContent(container.NewBorder(nil,
|
||||
container.NewVBox(widget.NewSeparator(), currentLrc),
|
||||
nil, nil,
|
||||
lrcWindow))
|
||||
w.Resize(fyne.NewSize(360, 540))
|
||||
w.CenterOnScreen()
|
||||
|
||||
// register handlers
|
||||
global.EventBus.Subscribe(eventChannel,
|
||||
events.PlayerLyricPosUpdate, "player.lyric.current_lyric", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
e := event.Data.(events.PlayerLyricPosUpdateEvent)
|
||||
logger.Debug("lyric update", e)
|
||||
if prevIndex >= len(fullLrc.Objects) || e.CurrentIndex >= len(fullLrc.Objects) {
|
||||
// fix race condition
|
||||
return
|
||||
}
|
||||
if e.CurrentIndex == -1 {
|
||||
currentLrc.SetText("")
|
||||
return
|
||||
}
|
||||
fullLrc.Objects[prevIndex].(*widget.Label).TextStyle.Bold = false
|
||||
fullLrc.Objects[prevIndex].Refresh()
|
||||
fullLrc.Objects[e.CurrentIndex].(*widget.Label).TextStyle.Bold = true
|
||||
fullLrc.Objects[e.CurrentIndex].Refresh()
|
||||
prevIndex = e.CurrentIndex
|
||||
currentLrc.SetText(e.CurrentLine.Lyric)
|
||||
lrcWindow.Scrolled(&fyne.ScrollEvent{
|
||||
Scrolled: fyne.Delta{
|
||||
DX: 0,
|
||||
DY: lrcWindow.Offset.Y - float32(e.CurrentIndex-2)/float32(e.Total)*lrcWindow.Content.Size().Height,
|
||||
},
|
||||
})
|
||||
fullLrc.Refresh()
|
||||
}))
|
||||
|
||||
global.EventBus.Subscribe(eventChannel, events.UpdateCurrentLyric, "player.lyric.current_lyric", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
e := event.Data.(events.UpdateCurrentLyricData)
|
||||
fullLrc.Objects = createLyricObj(&e.Lyrics)
|
||||
lrcWindow.Refresh()
|
||||
}))
|
||||
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.CmdGetCurrentLyric, events.CmdGetCurrentLyricData{})
|
||||
|
||||
w.SetOnClosed(func() {
|
||||
global.EventBus.Unsubscribe(events.UpdateCurrentLyric, "player.lyric.current_lyric")
|
||||
PlayController.LrcWindowOpen = false
|
||||
})
|
||||
return w
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package gui
|
||||
package config
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/component"
|
||||
"AynaLivePlayer/gui/gctx"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
@@ -37,13 +38,13 @@ func (b *bascicConfig) CreatePanel() fyne.CanvasObject {
|
||||
if b {
|
||||
mode = model.PlaylistModeRandom
|
||||
}
|
||||
logger.Infof("Set player playlist mode to %d", mode)
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.PlaylistModeChangeCmd(model.PlaylistIDPlayer),
|
||||
gctx.Logger.Infof("Set player playlist mode to %d", mode)
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.PlaylistModeChangeCmd(model.PlaylistIDPlayer),
|
||||
events.PlaylistModeChangeCmdEvent{
|
||||
Mode: mode,
|
||||
})
|
||||
})
|
||||
global.EventBus.Subscribe(eventChannel, events.PlaylistModeChangeUpdate(model.PlaylistIDPlayer),
|
||||
global.EventBus.Subscribe(gctx.EventChannel, events.PlaylistModeChangeUpdate(model.PlaylistIDPlayer),
|
||||
"gui.config.basic.random_playlist.player",
|
||||
gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
data := event.Data.(events.PlaylistModeChangeUpdateEvent)
|
||||
@@ -56,13 +57,13 @@ func (b *bascicConfig) CreatePanel() fyne.CanvasObject {
|
||||
if b {
|
||||
mode = model.PlaylistModeRandom
|
||||
}
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.PlaylistModeChangeCmd(model.PlaylistIDSystem),
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.PlaylistModeChangeCmd(model.PlaylistIDSystem),
|
||||
events.PlaylistModeChangeCmdEvent{
|
||||
Mode: mode,
|
||||
})
|
||||
})
|
||||
|
||||
global.EventBus.Subscribe(eventChannel, events.PlaylistModeChangeUpdate(model.PlaylistIDSystem),
|
||||
global.EventBus.Subscribe(gctx.EventChannel, events.PlaylistModeChangeUpdate(model.PlaylistIDSystem),
|
||||
"gui.config.basic.random_playlist.system",
|
||||
gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
data := event.Data.(events.PlaylistModeChangeUpdateEvent)
|
||||
@@ -80,11 +81,11 @@ func (b *bascicConfig) CreatePanel() fyne.CanvasObject {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.PlayerSetAudioDeviceCmd, events.PlayerSetAudioDeviceCmdEvent{
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.PlayerSetAudioDeviceCmd, events.PlayerSetAudioDeviceCmdEvent{
|
||||
Device: name,
|
||||
})
|
||||
})
|
||||
global.EventBus.Subscribe(eventChannel,
|
||||
global.EventBus.Subscribe(gctx.EventChannel,
|
||||
events.PlayerAudioDeviceUpdate,
|
||||
"gui.config.basic.audio_device.update",
|
||||
gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
@@ -99,7 +100,7 @@ func (b *bascicConfig) CreatePanel() fyne.CanvasObject {
|
||||
currentDevice = device.Description
|
||||
}
|
||||
}
|
||||
logger.Infof("update audio device. set current to %s (%s)", data.Current, deviceDesc2Name[data.Current])
|
||||
gctx.Logger.Infof("update audio device. set current to %s (%s)", data.Current, deviceDesc2Name[data.Current])
|
||||
deviceSel.Options = devices
|
||||
deviceSel.Selected = currentDevice
|
||||
deviceSel.Refresh()
|
||||
@@ -123,7 +124,7 @@ func (b *bascicConfig) CreatePanel() fyne.CanvasObject {
|
||||
config.General.AutoCheckUpdate),
|
||||
)
|
||||
checkUpdateBtn := widget.NewButton(i18n.T("gui.config.basic.check_update"), func() {
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.CheckUpdateCmd, events.CheckUpdateCmdEvent{})
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.CheckUpdateCmd, events.CheckUpdateCmdEvent{})
|
||||
})
|
||||
useSysPlaylistBtn := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("gui.config.basic.use_system_playlist")),
|
||||
@@ -1,4 +1,4 @@
|
||||
package gui
|
||||
package config
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/gui/component"
|
||||
@@ -21,7 +21,7 @@ func AddConfigLayout(cfgs ...ConfigLayout) {
|
||||
ConfigList = append(ConfigList, cfgs...)
|
||||
}
|
||||
|
||||
func createConfigLayout() fyne.CanvasObject {
|
||||
func CreateView() fyne.CanvasObject {
|
||||
// initialize config panels
|
||||
for _, c := range ConfigList {
|
||||
c.CreatePanel()
|
||||
@@ -1,9 +1,11 @@
|
||||
package gui
|
||||
package history
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/component"
|
||||
"AynaLivePlayer/gui/gctx"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
@@ -15,16 +17,34 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var History = &struct {
|
||||
Medias []model.Media
|
||||
List *widget.List
|
||||
mux sync.RWMutex
|
||||
}{}
|
||||
var medias []model.Media
|
||||
var listWidget *widget.List
|
||||
var mux sync.RWMutex
|
||||
|
||||
func CreateView() fyne.CanvasObject {
|
||||
view := createHistoryList()
|
||||
global.EventBus.Subscribe(gctx.EventChannel,
|
||||
events.PlayerPlayingUpdate,
|
||||
"gui.history.playing_update",
|
||||
gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
if event.Data.(events.PlayerPlayingUpdateEvent).Removed {
|
||||
return
|
||||
}
|
||||
mux.Lock()
|
||||
medias = append(medias, event.Data.(events.PlayerPlayingUpdateEvent).Media)
|
||||
if len(medias) > 1000 {
|
||||
medias = medias[len(medias)-1000:]
|
||||
}
|
||||
listWidget.Refresh()
|
||||
mux.Unlock()
|
||||
}))
|
||||
return view
|
||||
}
|
||||
|
||||
func createHistoryList() fyne.CanvasObject {
|
||||
History.List = widget.NewList(
|
||||
listWidget = widget.NewList(
|
||||
func() int {
|
||||
return len(History.Medias)
|
||||
return len(medias)
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
return container.NewBorder(nil, nil,
|
||||
@@ -34,12 +54,12 @@ func createHistoryList() fyne.CanvasObject {
|
||||
widget.NewButtonWithIcon("", theme.ContentAddIcon(), nil),
|
||||
),
|
||||
container.NewGridWithColumns(3,
|
||||
newLabelWithWrapping("title", fyne.TextTruncate),
|
||||
newLabelWithWrapping("artist", fyne.TextTruncate),
|
||||
newLabelWithWrapping("user", fyne.TextTruncate)))
|
||||
component.NewLabelWithOpts("title", component.LabelTruncation(fyne.TextTruncateClip)),
|
||||
component.NewLabelWithOpts("artist", component.LabelTruncation(fyne.TextTruncateClip)),
|
||||
component.NewLabelWithOpts("user", component.LabelTruncation(fyne.TextTruncateClip))))
|
||||
},
|
||||
func(id widget.ListItemID, object fyne.CanvasObject) {
|
||||
m := History.Medias[len(History.Medias)-id-1]
|
||||
m := medias[len(medias)-id-1]
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*widget.Label).SetText(
|
||||
m.Info.Title)
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(
|
||||
@@ -50,18 +70,17 @@ func createHistoryList() fyne.CanvasObject {
|
||||
btns := object.(*fyne.Container).Objects[2].(*fyne.Container).Objects
|
||||
m.User = model.SystemUser
|
||||
btns[0].(*widget.Button).OnTapped = func() {
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.PlayerPlayCmd, events.PlayerPlayCmdEvent{
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.PlayerPlayCmd, events.PlayerPlayCmdEvent{
|
||||
Media: m,
|
||||
})
|
||||
}
|
||||
btns[1].(*widget.Button).OnTapped = func() {
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.PlaylistInsertCmd(model.PlaylistIDPlayer), events.PlaylistInsertCmdEvent{
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.PlaylistInsertCmd(model.PlaylistIDPlayer), events.PlaylistInsertCmdEvent{
|
||||
Media: m,
|
||||
Position: -1,
|
||||
})
|
||||
}
|
||||
})
|
||||
registerHistoryHandler()
|
||||
return container.NewBorder(
|
||||
container.NewBorder(nil, nil,
|
||||
widget.NewLabel("#"), widget.NewLabel(i18n.T("gui.history.operation")),
|
||||
@@ -70,17 +89,6 @@ func createHistoryList() fyne.CanvasObject {
|
||||
widget.NewLabel(i18n.T("gui.history.artist")),
|
||||
widget.NewLabel(i18n.T("gui.history.user")))),
|
||||
nil, nil, nil,
|
||||
History.List,
|
||||
listWidget,
|
||||
)
|
||||
}
|
||||
|
||||
func registerHistoryHandler() {
|
||||
global.EventBus.Subscribe(eventChannel,
|
||||
events.PlaylistDetailUpdate(model.PlaylistIDHistory),
|
||||
"gui.history.update", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
History.mux.Lock()
|
||||
History.Medias = event.Data.(events.PlaylistDetailUpdateEvent).Medias
|
||||
History.List.Refresh()
|
||||
History.mux.Unlock()
|
||||
}))
|
||||
}
|
||||
195
gui/views/liverooms/liverooms.go
Normal file
195
gui/views/liverooms/liverooms.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package liverooms
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/gctx"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func CreateView() fyne.CanvasObject {
|
||||
view := container.NewBorder(nil, nil, createRoomSelector(), nil, createRoomController())
|
||||
registerRoomHandlers()
|
||||
return view
|
||||
}
|
||||
|
||||
var providers []model.LiveRoomProviderInfo = make([]model.LiveRoomProviderInfo, 0)
|
||||
var rooms []model.LiveRoom = make([]model.LiveRoom, 0)
|
||||
|
||||
var lock sync.RWMutex
|
||||
|
||||
var currentRoomView = &struct {
|
||||
roomTitle *widget.Label
|
||||
roomID *widget.Label
|
||||
status *widget.Label
|
||||
autoConnect *widget.Check
|
||||
connectBtn *widget.Button
|
||||
disConnectBtn *widget.Button
|
||||
}{}
|
||||
|
||||
var currentIndex int = 0
|
||||
|
||||
func getCurrentRoom() (model.LiveRoom, bool) {
|
||||
lock.RLock()
|
||||
if currentIndex >= len(rooms) {
|
||||
lock.RUnlock()
|
||||
return model.LiveRoom{}, false
|
||||
}
|
||||
room := rooms[currentIndex]
|
||||
lock.RUnlock()
|
||||
return room, true
|
||||
}
|
||||
|
||||
func renderCurrentRoom() {
|
||||
room, ok := getCurrentRoom()
|
||||
if !ok {
|
||||
currentRoomView.roomTitle.SetText("")
|
||||
currentRoomView.roomID.SetText("")
|
||||
currentRoomView.autoConnect.SetChecked(false)
|
||||
currentRoomView.status.SetText(i18n.T("gui.room.waiting"))
|
||||
return
|
||||
}
|
||||
currentRoomView.roomTitle.SetText(room.DisplayName())
|
||||
currentRoomView.roomID.SetText(room.LiveRoom.Identifier())
|
||||
currentRoomView.autoConnect.SetChecked(room.Config.AutoConnect)
|
||||
if room.Status {
|
||||
currentRoomView.status.SetText(i18n.T("gui.room.status.connected"))
|
||||
} else {
|
||||
currentRoomView.status.SetText(i18n.T("gui.room.status.disconnected"))
|
||||
}
|
||||
}
|
||||
|
||||
func registerRoomHandlers() {
|
||||
global.EventBus.Subscribe(gctx.EventChannel,
|
||||
events.LiveRoomProviderUpdate,
|
||||
"gui.liveroom.provider_update",
|
||||
gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
providers = event.Data.(events.LiveRoomProviderUpdateEvent).Providers
|
||||
//RoomTab.Rooms.Refresh()
|
||||
}))
|
||||
global.EventBus.Subscribe(gctx.EventChannel,
|
||||
events.UpdateLiveRoomRooms,
|
||||
"gui.liveroom.rooms_update",
|
||||
gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
gctx.Logger.Infof("Update rooms")
|
||||
lock.Lock()
|
||||
rooms = event.Data.(events.UpdateLiveRoomRoomsData).Rooms
|
||||
lock.Unlock()
|
||||
renderRoomList()
|
||||
renderCurrentRoom()
|
||||
}))
|
||||
global.EventBus.Subscribe(gctx.EventChannel,
|
||||
events.UpdateLiveRoomStatus,
|
||||
"gui.liveroom.room_status_update",
|
||||
gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
room := event.Data.(events.UpdateLiveRoomStatusData).Room
|
||||
lock.Lock()
|
||||
index := -1
|
||||
for i := 0; i < len(rooms); i++ {
|
||||
if rooms[i].LiveRoom.Identifier() == room.LiveRoom.Identifier() {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if index == -1 {
|
||||
lock.Unlock()
|
||||
return
|
||||
}
|
||||
rooms[index] = room
|
||||
lock.Unlock()
|
||||
if index == currentIndex {
|
||||
renderCurrentRoom()
|
||||
}
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
func createRoomController() fyne.CanvasObject {
|
||||
currentRoomView.connectBtn = widget.NewButton(i18n.T("gui.room.btn.connect"), func() {
|
||||
room, ok := getCurrentRoom()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
gctx.Logger.Infof("Connect to room %s", room.LiveRoom.Identifier())
|
||||
currentRoomView.connectBtn.Disable()
|
||||
go func() {
|
||||
resp, err := global.EventBus.Call(events.CmdLiveRoomOperation, events.ReplyLiveRoomOperation, events.CmdLiveRoomOperationData{
|
||||
Identifier: room.LiveRoom.Identifier(),
|
||||
SetConnect: true,
|
||||
})
|
||||
if err != nil {
|
||||
gctx.Logger.Errorf("failed to connect to room %s", room.LiveRoom.Identifier())
|
||||
gutil.RunInFyneThread(currentRoomView.connectBtn.Enable)
|
||||
return
|
||||
}
|
||||
if resp.Data.(events.ReplyLiveRoomOperationData).Err != nil {
|
||||
err = resp.Data.(events.ReplyLiveRoomOperationData).Err
|
||||
}
|
||||
if err != nil {
|
||||
// todo: show error
|
||||
}
|
||||
gutil.RunInFyneThread(currentRoomView.connectBtn.Enable)
|
||||
}()
|
||||
})
|
||||
currentRoomView.disConnectBtn = widget.NewButton(i18n.T("gui.room.btn.disconnect"), func() {
|
||||
room, ok := getCurrentRoom()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
gctx.Logger.Infof("disconnect to room %s", room.LiveRoom.Identifier())
|
||||
currentRoomView.disConnectBtn.Disable()
|
||||
go func() {
|
||||
resp, err := global.EventBus.Call(events.CmdLiveRoomOperation, events.ReplyLiveRoomOperation, events.CmdLiveRoomOperationData{
|
||||
Identifier: room.LiveRoom.Identifier(),
|
||||
SetConnect: false,
|
||||
})
|
||||
if err != nil {
|
||||
gctx.Logger.Errorf("failed to disconnect to room %s", room.LiveRoom.Identifier())
|
||||
gutil.RunInFyneThread(currentRoomView.disConnectBtn.Enable)
|
||||
return
|
||||
}
|
||||
if resp.Data.(events.ReplyLiveRoomOperationData).Err != nil {
|
||||
err = resp.Data.(events.ReplyLiveRoomOperationData).Err
|
||||
}
|
||||
if err != nil {
|
||||
// todo: show error
|
||||
}
|
||||
gutil.RunInFyneThread(currentRoomView.disConnectBtn.Enable)
|
||||
}()
|
||||
})
|
||||
|
||||
currentRoomView.status = widget.NewLabel(i18n.T("gui.room.waiting"))
|
||||
currentRoomView.roomTitle = widget.NewLabel("")
|
||||
currentRoomView.roomID = widget.NewLabel("")
|
||||
|
||||
currentRoomView.autoConnect = widget.NewCheck(i18n.T("gui.room.check.autoconnect"), func(b bool) {
|
||||
room, ok := getCurrentRoom()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
gctx.Logger.Infof("Change room %s autoconnect to %v", room.LiveRoom.Identifier(), b)
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel,
|
||||
events.CmdLiveRoomConfigChange,
|
||||
events.CmdLiveRoomConfigChangeData{
|
||||
Identifier: room.LiveRoom.Identifier(),
|
||||
Config: model.LiveRoomConfig{
|
||||
AutoConnect: b,
|
||||
},
|
||||
})
|
||||
return
|
||||
})
|
||||
return container.NewVBox(
|
||||
currentRoomView.roomTitle,
|
||||
currentRoomView.roomID,
|
||||
currentRoomView.status,
|
||||
container.NewHBox(widget.NewLabel(i18n.T("gui.room.check.autoconnect")), currentRoomView.autoConnect),
|
||||
container.NewHBox(currentRoomView.connectBtn, currentRoomView.disConnectBtn),
|
||||
)
|
||||
}
|
||||
120
gui/views/liverooms/selector.go
Normal file
120
gui/views/liverooms/selector.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package liverooms
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/gctx"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
var roomSelectorView = &struct {
|
||||
rooms *widget.List
|
||||
addBtn *widget.Button
|
||||
removeBtn *widget.Button
|
||||
}{}
|
||||
|
||||
func renderRoomList() {
|
||||
lock.Lock()
|
||||
roomSelectorView.rooms.Refresh()
|
||||
lock.Unlock()
|
||||
}
|
||||
|
||||
func createRoomSelector() fyne.CanvasObject {
|
||||
roomSelectorView.rooms = widget.NewList(
|
||||
func() int {
|
||||
return len(rooms)
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
return widget.NewLabel("AAAAAAAAAAAAAAAA")
|
||||
},
|
||||
func(id widget.ListItemID, object fyne.CanvasObject) {
|
||||
object.(*widget.Label).SetText(
|
||||
rooms[id].DisplayName())
|
||||
})
|
||||
roomSelectorView.addBtn = widget.NewButton(i18n.T("gui.room.button.add"), func() {
|
||||
providerNames := make([]string, len(providers))
|
||||
for i := 0; i < len(providers); i++ {
|
||||
providerNames[i] = providers[i].Name
|
||||
}
|
||||
descriptionLabel := widget.NewLabel(i18n.T("gui.room.add.prompt"))
|
||||
clientNameEntry := widget.NewSelect(providerNames, func(s string) {
|
||||
for i := 0; i < len(providers); i++ {
|
||||
if providers[i].Name == s {
|
||||
descriptionLabel.SetText(i18n.T(providers[i].Description))
|
||||
break
|
||||
}
|
||||
descriptionLabel.SetText("")
|
||||
}
|
||||
})
|
||||
idEntry := widget.NewEntry()
|
||||
nameEntry := widget.NewEntry()
|
||||
dia := dialog.NewCustomConfirm(
|
||||
i18n.T("gui.room.add.title"),
|
||||
i18n.T("gui.room.add.confirm"),
|
||||
i18n.T("gui.room.add.cancel"),
|
||||
container.NewVBox(
|
||||
container.New(
|
||||
layout.NewFormLayout(),
|
||||
widget.NewLabel(i18n.T("gui.room.add.name")),
|
||||
nameEntry,
|
||||
widget.NewLabel(i18n.T("gui.room.add.client_name")),
|
||||
clientNameEntry,
|
||||
widget.NewLabel(i18n.T("gui.room.add.id_url")),
|
||||
idEntry,
|
||||
),
|
||||
descriptionLabel,
|
||||
),
|
||||
func(b bool) {
|
||||
if b && len(clientNameEntry.Selected) > 0 && len(idEntry.Text) > 0 {
|
||||
gctx.Logger.Infof("Add room %s %s", clientNameEntry.Selected, idEntry.Text)
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel,
|
||||
events.CmdLiveRoomAdd,
|
||||
events.CmdLiveRoomAddData{
|
||||
Title: nameEntry.Text,
|
||||
Provider: clientNameEntry.Selected,
|
||||
RoomKey: idEntry.Text,
|
||||
})
|
||||
}
|
||||
},
|
||||
gctx.Context.Window,
|
||||
)
|
||||
dia.Resize(fyne.NewSize(512, 256))
|
||||
dia.Show()
|
||||
})
|
||||
roomSelectorView.removeBtn = widget.NewButton(i18n.T("gui.room.button.remove"), func() {
|
||||
room, ok := getCurrentRoom()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel,
|
||||
events.CmdLiveRoomRemove,
|
||||
events.CmdLiveRoomRemoveData{
|
||||
Identifier: room.LiveRoom.Identifier(),
|
||||
})
|
||||
})
|
||||
roomSelectorView.rooms.OnSelected = func(id widget.ListItemID) {
|
||||
if id >= len(rooms) {
|
||||
return
|
||||
}
|
||||
currentIndex = id
|
||||
room, ok := getCurrentRoom()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
gctx.Logger.Infof("Select room %s", room.LiveRoom.Identifier())
|
||||
renderCurrentRoom()
|
||||
}
|
||||
return container.NewHBox(
|
||||
container.NewBorder(
|
||||
nil, container.NewCenter(container.NewHBox(roomSelectorView.addBtn, roomSelectorView.removeBtn)),
|
||||
nil, nil,
|
||||
roomSelectorView.rooms,
|
||||
),
|
||||
widget.NewSeparator(),
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package gui
|
||||
package player
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/component"
|
||||
"AynaLivePlayer/gui/gctx"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
@@ -45,22 +46,22 @@ var PlayController = &PlayControllerContainer{}
|
||||
|
||||
func registerPlayControllerHandler() {
|
||||
PlayController.ButtonPrev.OnTapped = func() {
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.PlayerSeekCmd, events.PlayerSeekCmdEvent{
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.PlayerSeekCmd, events.PlayerSeekCmdEvent{
|
||||
Position: 0,
|
||||
Absolute: true,
|
||||
})
|
||||
}
|
||||
PlayController.ButtonSwitch.OnTapped = func() {
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.PlayerToggleCmd, events.PlayerToggleCmdEvent{})
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.PlayerToggleCmd, events.PlayerToggleCmdEvent{})
|
||||
}
|
||||
PlayController.ButtonNext.OnTapped = func() {
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.PlayerPlayNextCmd, events.PlayerPlayNextCmdEvent{})
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.PlayerPlayNextCmd, events.PlayerPlayNextCmdEvent{})
|
||||
}
|
||||
|
||||
PlayController.ButtonLrc.OnTapped = func() {
|
||||
if !PlayController.LrcWindowOpen {
|
||||
PlayController.LrcWindowOpen = true
|
||||
createLyricWindow().Show()
|
||||
createLyricWindowV2().Show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +69,7 @@ func registerPlayControllerHandler() {
|
||||
showPlayerWindow()
|
||||
}
|
||||
|
||||
global.EventBus.Subscribe(eventChannel, events.PlayerPropertyPauseUpdate, "gui.player.controller.paused", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
global.EventBus.Subscribe(gctx.EventChannel, events.PlayerPropertyPauseUpdate, "gui.player.controller.paused", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
if event.Data.(events.PlayerPropertyPauseUpdateEvent).Paused {
|
||||
PlayController.ButtonSwitch.Icon = theme.MediaPlayIcon()
|
||||
} else {
|
||||
@@ -77,15 +78,15 @@ func registerPlayControllerHandler() {
|
||||
PlayController.ButtonSwitch.Refresh()
|
||||
}))
|
||||
|
||||
global.EventBus.Subscribe(eventChannel, events.PlayerPropertyPercentPosUpdate, "gui.player.controller.percent_pos", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
global.EventBus.Subscribe(gctx.EventChannel, events.PlayerPropertyPercentPosUpdate, "gui.player.controller.percent_pos", func(event *eventbus.Event) {
|
||||
if PlayController.Progress.Dragging {
|
||||
return
|
||||
}
|
||||
PlayController.Progress.Value = event.Data.(events.PlayerPropertyPercentPosUpdateEvent).PercentPos * 10
|
||||
PlayController.Progress.Refresh()
|
||||
}))
|
||||
gutil.RunInFyneThread(PlayController.Progress.Refresh)
|
||||
})
|
||||
|
||||
global.EventBus.Subscribe(eventChannel, events.PlayerPropertyStateUpdate, "gui.player.controller.idle_active", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
global.EventBus.Subscribe(gctx.EventChannel, events.PlayerPropertyStateUpdate, "gui.player.controller.idle_active", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
state := event.Data.(events.PlayerPropertyStateUpdateEvent).State
|
||||
if state == model.PlayerStateIdle || state == model.PlayerStateLoading {
|
||||
PlayController.Progress.Value = 0
|
||||
@@ -101,33 +102,33 @@ func registerPlayControllerHandler() {
|
||||
|
||||
PlayController.Progress.Max = 0
|
||||
PlayController.Progress.OnDragEnd = func(f float64) {
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.PlayerSeekCmd, events.PlayerSeekCmdEvent{
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.PlayerSeekCmd, events.PlayerSeekCmdEvent{
|
||||
Position: f / 10,
|
||||
Absolute: false,
|
||||
})
|
||||
}
|
||||
|
||||
global.EventBus.Subscribe(eventChannel, events.PlayerPropertyTimePosUpdate, "gui.player.controller.time_pos", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
global.EventBus.Subscribe(gctx.EventChannel, events.PlayerPropertyTimePosUpdate, "gui.player.controller.time_pos", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
PlayController.CurrentTime.SetText(util.FormatTime(int(event.Data.(events.PlayerPropertyTimePosUpdateEvent).TimePos)))
|
||||
}))
|
||||
|
||||
global.EventBus.Subscribe(eventChannel, events.PlayerPropertyDurationUpdate, "gui.player.controller.duration", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
global.EventBus.Subscribe(gctx.EventChannel, events.PlayerPropertyDurationUpdate, "gui.player.controller.duration", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
PlayController.TotalTime.SetText(util.FormatTime(int(event.Data.(events.PlayerPropertyDurationUpdateEvent).Duration)))
|
||||
}))
|
||||
|
||||
global.EventBus.Subscribe(eventChannel, events.PlayerPropertyVolumeUpdate, "gui.player.controller.volume", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
global.EventBus.Subscribe(gctx.EventChannel, events.PlayerPropertyVolumeUpdate, "gui.player.controller.volume", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
PlayController.Volume.Value = event.Data.(events.PlayerPropertyVolumeUpdateEvent).Volume
|
||||
PlayController.Volume.Refresh()
|
||||
}))
|
||||
|
||||
PlayController.Volume.OnChanged = func(f float64) {
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.PlayerVolumeChangeCmd, events.PlayerVolumeChangeCmdEvent{
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.PlayerVolumeChangeCmd, events.PlayerVolumeChangeCmdEvent{
|
||||
Volume: f,
|
||||
})
|
||||
}
|
||||
|
||||
// todo: double check cover loading for new thread model
|
||||
global.EventBus.Subscribe(eventChannel, events.PlayerPlayingUpdate, "gui.player.updateinfo", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
global.EventBus.Subscribe(gctx.EventChannel, events.PlayerPlayingUpdate, "gui.player.updateinfo", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
if event.Data.(events.PlayerPlayingUpdateEvent).Removed {
|
||||
PlayController.Progress.Value = 0
|
||||
PlayController.Progress.Max = 0
|
||||
@@ -163,7 +164,7 @@ func registerPlayControllerHandler() {
|
||||
picture, err := gutil.NewImageFromPlayerPicture(media.Info.Cover)
|
||||
if err != nil {
|
||||
ch <- nil
|
||||
logger.Errorf("fail to load cover: %v", err)
|
||||
gctx.Logger.Errorf("fail to load cover: %v", err)
|
||||
return
|
||||
}
|
||||
ch <- picture
|
||||
@@ -219,12 +220,12 @@ func createPlayControllerV2() fyne.CanvasObject {
|
||||
controls.SeparatorThickness = 0
|
||||
|
||||
PlayController.Progress = component.NewSliderPlus(0, 1000)
|
||||
PlayController.CurrentTime = widget.NewLabel("0:00")
|
||||
PlayController.TotalTime = widget.NewLabel("0:00")
|
||||
PlayController.CurrentTime = widget.NewLabel("00:00")
|
||||
PlayController.TotalTime = widget.NewLabel("00:00")
|
||||
progressItem := container.NewBorder(nil, nil,
|
||||
PlayController.CurrentTime,
|
||||
nil,
|
||||
PlayController.TotalTime,
|
||||
PlayController.Progress)
|
||||
component.NewFixedHSplitContainer(PlayController.CurrentTime, PlayController.Progress, 0.1))
|
||||
|
||||
PlayController.Title = widget.NewLabel("Title")
|
||||
PlayController.Title.Truncation = fyne.TextTruncateClip
|
||||
@@ -1,13 +1,14 @@
|
||||
package gui
|
||||
package player
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/gctx"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
)
|
||||
|
||||
func registerHandlers() {
|
||||
global.EventBus.Subscribe(eventChannel, events.GUISetPlayerWindowOpenCmd, "gui.player.videoplayer.handleopen", func(event *eventbus.Event) {
|
||||
global.EventBus.Subscribe(gctx.EventChannel, events.GUISetPlayerWindowOpenCmd, "gui.player.videoplayer.handleopen", func(event *eventbus.Event) {
|
||||
data := event.Data.(events.GUISetPlayerWindowOpenCmdEvent)
|
||||
if data.SetOpen {
|
||||
playerWindow.Close()
|
||||
98
gui/views/player/lyric.go
Normal file
98
gui/views/player/lyric.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package player
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/component/lyrics"
|
||||
"AynaLivePlayer/gui/gctx"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"github.com/AynaLivePlayer/miaosic"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var lyricWindow fyne.Window = nil
|
||||
var lyricViewer *lyrics.LyricsViewer = nil
|
||||
var currLyrics []string
|
||||
var currentLrcObj miaosic.Lyrics = miaosic.Lyrics{}
|
||||
var lrcmux sync.RWMutex
|
||||
|
||||
func setupLyricViewer() {
|
||||
if lyricWindow != nil {
|
||||
return
|
||||
}
|
||||
lyricViewer = lyrics.NewLyricsViewer()
|
||||
lyricViewer.ActiveLyricPosition = lyrics.ActiveLyricPositionUpperMiddle
|
||||
lyricViewer.Alignment = fyne.TextAlignCenter
|
||||
lyricViewer.HoveredLyricColorName = theme.ColorNameDisabled
|
||||
lyricViewer.SetLyrics([]string{""}, true)
|
||||
lyricViewer.OnLyricTapped = func(lineNum int) {
|
||||
lineNum = lineNum - 1
|
||||
if lineNum < 0 {
|
||||
return
|
||||
}
|
||||
lrcmux.Lock()
|
||||
if lineNum >= len(currentLrcObj.Content) {
|
||||
lrcmux.Unlock()
|
||||
return
|
||||
}
|
||||
line := currentLrcObj.Content[lineNum]
|
||||
lrcmux.Unlock()
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.PlayerSeekCmd, events.PlayerSeekCmdEvent{
|
||||
Position: line.Time,
|
||||
Absolute: true,
|
||||
})
|
||||
}
|
||||
|
||||
global.EventBus.Subscribe(gctx.EventChannel, events.UpdateCurrentLyric, "player.lyric.current_lyric", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
e := event.Data.(events.UpdateCurrentLyricData)
|
||||
tmpLyric := make([]string, 0)
|
||||
for _, l := range e.Lyrics.Content {
|
||||
tmpLyric = append(tmpLyric, l.Lyric)
|
||||
}
|
||||
// ensure at least one line
|
||||
if len(tmpLyric) == 0 {
|
||||
tmpLyric = append(tmpLyric, "")
|
||||
}
|
||||
lrcmux.Lock()
|
||||
currentLrcObj = event.Data.(events.UpdateCurrentLyricData).Lyrics
|
||||
currLyrics = tmpLyric
|
||||
lyricViewer.SetLyrics(currLyrics, true)
|
||||
lyricViewer.SetCurrentLine(0)
|
||||
lrcmux.Unlock()
|
||||
}))
|
||||
|
||||
// register handlers
|
||||
global.EventBus.Subscribe(gctx.EventChannel,
|
||||
events.PlayerLyricPosUpdate, "player.lyric.lyric_pos_update", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
e := event.Data.(events.PlayerLyricPosUpdateEvent)
|
||||
gctx.Logger.Debug("lyric update", e)
|
||||
lrcmux.Lock()
|
||||
if e.CurrentIndex >= len(currLyrics) {
|
||||
// fix race condition
|
||||
lrcmux.Unlock()
|
||||
return
|
||||
}
|
||||
index := 0
|
||||
if e.CurrentIndex != -1 {
|
||||
index = e.CurrentIndex
|
||||
}
|
||||
lyricViewer.SetCurrentLine(index + 1)
|
||||
lrcmux.Unlock()
|
||||
}))
|
||||
}
|
||||
|
||||
func createLyricWindowV2() fyne.Window {
|
||||
// create widgets
|
||||
lyricWindow = gctx.Context.App.NewWindow(i18n.T("gui.lyric.title"))
|
||||
lyricWindow.SetContent(lyricViewer)
|
||||
lyricWindow.Resize(fyne.NewSize(360, 540))
|
||||
lyricWindow.CenterOnScreen()
|
||||
lyricWindow.SetOnClosed(func() {
|
||||
PlayController.LrcWindowOpen = false
|
||||
})
|
||||
return lyricWindow
|
||||
}
|
||||
19
gui/views/player/player.go
Normal file
19
gui/views/player/player.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package player
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/gui/gctx"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
)
|
||||
|
||||
func CreateView() fyne.CanvasObject {
|
||||
setupLyricViewer()
|
||||
registerHandlers()
|
||||
gctx.Context.OnMainWindowClosing(func() {
|
||||
if playerWindow != nil {
|
||||
gctx.Logger.Infof("closing player window")
|
||||
playerWindow.Close()
|
||||
}
|
||||
})
|
||||
return container.NewBorder(nil, createPlayControllerV2(), nil, nil, createPlaylist())
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
package gui
|
||||
package player
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/component"
|
||||
"AynaLivePlayer/gui/gctx"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
@@ -28,12 +30,12 @@ func (b *playlistOperationButton) Tapped(e *fyne.PointEvent) {
|
||||
func newPlaylistOperationButton() *playlistOperationButton {
|
||||
b := &playlistOperationButton{Index: 0}
|
||||
deleteItem := fyne.NewMenuItem(i18n.T("gui.player.playlist.op.delete"), func() {
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.PlaylistDeleteCmd(model.PlaylistIDPlayer), events.PlaylistDeleteCmdEvent{
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.PlaylistDeleteCmd(model.PlaylistIDPlayer), events.PlaylistDeleteCmdEvent{
|
||||
Index: b.Index,
|
||||
})
|
||||
})
|
||||
topItem := fyne.NewMenuItem(i18n.T("gui.player.playlist.op.top"), func() {
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.PlaylistMoveCmd(model.PlaylistIDPlayer), events.PlaylistMoveCmdEvent{
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.PlaylistMoveCmd(model.PlaylistIDPlayer), events.PlaylistMoveCmdEvent{
|
||||
From: b.Index,
|
||||
To: 0,
|
||||
})
|
||||
@@ -61,9 +63,9 @@ func createPlaylist() fyne.CanvasObject {
|
||||
func() fyne.CanvasObject {
|
||||
return container.NewBorder(nil, nil, widget.NewLabel("index"), newPlaylistOperationButton(),
|
||||
container.NewGridWithColumns(3,
|
||||
newLabelWithWrapping("title", fyne.TextTruncate),
|
||||
newLabelWithWrapping("artist", fyne.TextTruncate),
|
||||
newLabelWithWrapping("user", fyne.TextTruncate)))
|
||||
component.NewLabelWithOpts("title", component.LabelTruncation(fyne.TextTruncateClip)),
|
||||
component.NewLabelWithOpts("artist", component.LabelTruncation(fyne.TextTruncateClip)),
|
||||
component.NewLabelWithOpts("user", component.LabelTruncation(fyne.TextTruncateClip))))
|
||||
},
|
||||
func(id widget.ListItemID, object fyne.CanvasObject) {
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*widget.Label).SetText(
|
||||
@@ -75,7 +77,7 @@ 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.EventBus.Subscribe(eventChannel, events.PlaylistDetailUpdate(model.PlaylistIDPlayer), "gui.player.playlist.update", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
global.EventBus.Subscribe(gctx.EventChannel, events.PlaylistDetailUpdate(model.PlaylistIDPlayer), "gui.player.playlist.update", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
UserPlaylist.mux.Lock()
|
||||
UserPlaylist.Medias = event.Data.(events.PlaylistDetailUpdateEvent).Medias
|
||||
UserPlaylist.List.Refresh()
|
||||
@@ -1,14 +1,18 @@
|
||||
package gui
|
||||
package player
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/gctx"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
var playerWindow fyne.Window
|
||||
var playerWindowHandle uintptr
|
||||
|
||||
func setupPlayerWindow() {
|
||||
playerWindow = App.NewWindow("CorePlayerPreview")
|
||||
playerWindow = gctx.Context.App.NewWindow("CorePlayerPreview")
|
||||
playerWindow.Resize(fyne.NewSize(480, 240))
|
||||
playerWindow.SetCloseIntercept(func() {
|
||||
playerWindow.Hide()
|
||||
@@ -23,9 +27,9 @@ func showPlayerWindow() {
|
||||
playerWindow.Show()
|
||||
if playerWindowHandle == 0 {
|
||||
playerWindowHandle = gutil.GetWindowHandle(playerWindow)
|
||||
logger.Infof("video output window handle: %d", playerWindowHandle)
|
||||
gctx.Logger.Infof("video output window handle: %d", playerWindowHandle)
|
||||
if playerWindowHandle != 0 {
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.PlayerVideoPlayerSetWindowHandleCmd,
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.PlayerVideoPlayerSetWindowHandleCmd,
|
||||
events.PlayerVideoPlayerSetWindowHandleCmdEvent{Handle: playerWindowHandle})
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
package gui
|
||||
package playlists
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/component"
|
||||
"AynaLivePlayer/gui/gctx"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
@@ -33,6 +35,10 @@ type PlaylistsTab struct {
|
||||
|
||||
var PlaylistManager = &PlaylistsTab{}
|
||||
|
||||
func CreateView() fyne.CanvasObject {
|
||||
return container.NewBorder(nil, nil, createPlaylists(), nil, createPlaylistMedias())
|
||||
}
|
||||
|
||||
func createPlaylists() fyne.CanvasObject {
|
||||
PlaylistManager.Playlists = widget.NewList(
|
||||
func() int {
|
||||
@@ -63,8 +69,8 @@ func createPlaylists() fyne.CanvasObject {
|
||||
),
|
||||
func(b bool) {
|
||||
if b && len(providerEntry.Selected) > 0 && len(idEntry.Text) > 0 {
|
||||
logger.Infof("add playlists %s %s", providerEntry.Selected, idEntry.Text)
|
||||
_ = global.EventBus.PublishToChannel(eventChannel,
|
||||
gctx.Logger.Infof("add playlists %s %s", providerEntry.Selected, idEntry.Text)
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel,
|
||||
events.PlaylistManagerAddPlaylistCmd,
|
||||
events.PlaylistManagerAddPlaylistCmdEvent{
|
||||
Provider: providerEntry.Selected,
|
||||
@@ -72,7 +78,7 @@ func createPlaylists() fyne.CanvasObject {
|
||||
})
|
||||
}
|
||||
},
|
||||
MainWindow,
|
||||
gctx.Context.Window,
|
||||
)
|
||||
dia.Resize(fyne.NewSize(512, 256))
|
||||
dia.Show()
|
||||
@@ -81,8 +87,8 @@ func createPlaylists() fyne.CanvasObject {
|
||||
if PlaylistManager.Index >= len(PlaylistManager.currentPlaylists) {
|
||||
return
|
||||
}
|
||||
logger.Infof("remove playlists %s", PlaylistManager.currentPlaylists[PlaylistManager.Index].Meta.ID())
|
||||
_ = global.EventBus.PublishToChannel(eventChannel,
|
||||
gctx.Logger.Infof("remove playlists %s", PlaylistManager.currentPlaylists[PlaylistManager.Index].Meta.ID())
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel,
|
||||
events.PlaylistManagerRemovePlaylistCmd,
|
||||
events.PlaylistManagerRemovePlaylistCmdEvent{
|
||||
PlaylistID: PlaylistManager.currentPlaylists[PlaylistManager.Index].Meta.ID(),
|
||||
@@ -93,29 +99,29 @@ func createPlaylists() fyne.CanvasObject {
|
||||
return
|
||||
}
|
||||
PlaylistManager.Index = id
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.PlaylistManagerGetCurrentCmd, events.PlaylistManagerGetCurrentCmdEvent{
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.PlaylistManagerGetCurrentCmd, events.PlaylistManagerGetCurrentCmdEvent{
|
||||
PlaylistID: PlaylistManager.currentPlaylists[id].Meta.ID(),
|
||||
})
|
||||
}
|
||||
global.EventBus.Subscribe(eventChannel, events.MediaProviderUpdate,
|
||||
global.EventBus.Subscribe(gctx.EventChannel, events.MediaProviderUpdate,
|
||||
"gui.playlists.provider.update", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
providers := event.Data.(events.MediaProviderUpdateEvent)
|
||||
s := make([]string, len(providers.Providers))
|
||||
copy(s, providers.Providers)
|
||||
PlaylistManager.providers = s
|
||||
}))
|
||||
global.EventBus.Subscribe(eventChannel, events.PlaylistManagerInfoUpdate,
|
||||
global.EventBus.Subscribe(gctx.EventChannel, events.PlaylistManagerInfoUpdate,
|
||||
"gui.playlists.info.update", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
data := event.Data.(events.PlaylistManagerInfoUpdateEvent)
|
||||
prevLen := len(PlaylistManager.currentPlaylists)
|
||||
PlaylistManager.currentPlaylists = data.Playlists
|
||||
logger.Infof("receive playlist info update, try to refresh playlists. prevLen=%d, newLen=%d", prevLen, len(PlaylistManager.currentPlaylists))
|
||||
gctx.Logger.Infof("receive playlist info update, try to refresh playlists. prevLen=%d, newLen=%d", prevLen, len(PlaylistManager.currentPlaylists))
|
||||
PlaylistManager.Playlists.Refresh()
|
||||
if prevLen != len(PlaylistManager.currentPlaylists) {
|
||||
PlaylistManager.Playlists.Select(0)
|
||||
}
|
||||
}))
|
||||
global.EventBus.Subscribe(eventChannel, events.PlaylistManagerSystemUpdate,
|
||||
global.EventBus.Subscribe(gctx.EventChannel, events.PlaylistManagerSystemUpdate,
|
||||
"gui.playlists.system.update", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
data := event.Data.(events.PlaylistManagerSystemUpdateEvent)
|
||||
PlaylistManager.CurrentSystemPlaylist.SetText(i18n.T("gui.playlist.current") + data.Info.DisplayName())
|
||||
@@ -137,7 +143,7 @@ func createPlaylistMedias() fyne.CanvasObject {
|
||||
if PlaylistManager.Index >= len(PlaylistManager.currentPlaylists) {
|
||||
return
|
||||
}
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.PlaylistManagerRefreshCurrentCmd, events.PlaylistManagerRefreshCurrentCmdEvent{
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.PlaylistManagerRefreshCurrentCmd, events.PlaylistManagerRefreshCurrentCmdEvent{
|
||||
PlaylistID: PlaylistManager.currentPlaylists[PlaylistManager.Index].Meta.ID(),
|
||||
})
|
||||
})
|
||||
@@ -147,8 +153,8 @@ func createPlaylistMedias() fyne.CanvasObject {
|
||||
if PlaylistManager.Index >= len(PlaylistManager.currentPlaylists) {
|
||||
return
|
||||
}
|
||||
logger.Infof("set playlist %s as system", PlaylistManager.currentPlaylists[PlaylistManager.Index].Meta.ID())
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.PlaylistManagerSetSystemCmd, events.PlaylistManagerSetSystemCmdEvent{
|
||||
gctx.Logger.Infof("set playlist %s as system", PlaylistManager.currentPlaylists[PlaylistManager.Index].Meta.ID())
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.PlaylistManagerSetSystemCmd, events.PlaylistManagerSetSystemCmdEvent{
|
||||
PlaylistID: PlaylistManager.currentPlaylists[PlaylistManager.Index].Meta.ID(),
|
||||
})
|
||||
})
|
||||
@@ -166,8 +172,8 @@ func createPlaylistMedias() fyne.CanvasObject {
|
||||
widget.NewButtonWithIcon("", theme.ContentAddIcon(), nil),
|
||||
),
|
||||
container.NewGridWithColumns(2,
|
||||
newLabelWithWrapping("title", fyne.TextTruncate),
|
||||
newLabelWithWrapping("artist", fyne.TextTruncate)))
|
||||
component.NewLabelWithOpts("title", component.LabelTruncation(fyne.TextTruncateClip)),
|
||||
component.NewLabelWithOpts("artist", component.LabelTruncation(fyne.TextTruncateClip))))
|
||||
},
|
||||
func(id widget.ListItemID, object fyne.CanvasObject) {
|
||||
m := PlaylistManager.currentMedias[id]
|
||||
@@ -179,20 +185,20 @@ func createPlaylistMedias() fyne.CanvasObject {
|
||||
btns := object.(*fyne.Container).Objects[2].(*fyne.Container).Objects
|
||||
m.User = model.SystemUser
|
||||
btns[0].(*widget.Button).OnTapped = func() {
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.PlayerPlayCmd, events.PlayerPlayCmdEvent{
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.PlayerPlayCmd, events.PlayerPlayCmdEvent{
|
||||
Media: m,
|
||||
})
|
||||
}
|
||||
btns[1].(*widget.Button).OnTapped = func() {
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.PlaylistInsertCmd(model.PlaylistIDPlayer), events.PlaylistInsertCmdEvent{
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.PlaylistInsertCmd(model.PlaylistIDPlayer), events.PlaylistInsertCmdEvent{
|
||||
Media: m,
|
||||
Position: -1,
|
||||
})
|
||||
}
|
||||
})
|
||||
global.EventBus.Subscribe(eventChannel, events.PlaylistManagerCurrentUpdate,
|
||||
global.EventBus.Subscribe(gctx.EventChannel, events.PlaylistManagerCurrentUpdate,
|
||||
"gui.playlists.current.update", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
logger.Infof("receive current playlist update, try to refresh playlist medias")
|
||||
gctx.Logger.Infof("receive current playlist update, try to refresh playlist medias")
|
||||
data := event.Data.(events.PlaylistManagerCurrentUpdateEvent)
|
||||
PlaylistManager.currentMedias = data.Medias
|
||||
PlaylistManager.PlaylistMedia.Refresh()
|
||||
10
gui/views/search/search.go
Normal file
10
gui/views/search/search.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
)
|
||||
|
||||
func CreateView() fyne.CanvasObject {
|
||||
return container.NewBorder(createSearchBar(), nil, nil, nil, createSearchList())
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package gui
|
||||
package search
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/component"
|
||||
"AynaLivePlayer/gui/gctx"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
@@ -30,18 +31,18 @@ func createSearchBar() fyne.CanvasObject {
|
||||
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)
|
||||
gctx.Logger.Debugf("Search keyword: %s, provider: %s", keyword, pr)
|
||||
SearchResult.mux.Lock()
|
||||
SearchResult.Items = make([]model.Media, 0)
|
||||
SearchResult.List.Refresh()
|
||||
SearchResult.mux.Unlock()
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.SearchCmd, events.SearchCmdEvent{
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.CmdMiaosicSearch, events.CmdMiaosicSearchData{
|
||||
Keyword: keyword,
|
||||
Provider: pr,
|
||||
})
|
||||
})
|
||||
|
||||
global.EventBus.Subscribe(eventChannel, events.MediaProviderUpdate,
|
||||
global.EventBus.Subscribe(gctx.EventChannel, events.MediaProviderUpdate,
|
||||
"gui.search.provider.update", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
providers := event.Data.(events.MediaProviderUpdateEvent)
|
||||
s := make([]string, len(providers.Providers))
|
||||
@@ -52,8 +53,7 @@ func createSearchBar() fyne.CanvasObject {
|
||||
}
|
||||
}))
|
||||
|
||||
SearchBar.UseSource = widget.NewSelect([]string{}, func(s string) {
|
||||
})
|
||||
SearchBar.UseSource = widget.NewSelect([]string{}, func(s string) {})
|
||||
|
||||
searchInput := container.NewBorder(
|
||||
nil, nil, widget.NewLabel(i18n.T("gui.search.search")), SearchBar.Button,
|
||||
@@ -1,9 +1,11 @@
|
||||
package gui
|
||||
package search
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/component"
|
||||
"AynaLivePlayer/gui/gctx"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
@@ -37,9 +39,9 @@ func createSearchList() fyne.CanvasObject {
|
||||
widget.NewButtonWithIcon("", theme.ContentAddIcon(), nil),
|
||||
),
|
||||
container.NewGridWithColumns(3,
|
||||
newLabelWithWrapping("title", fyne.TextTruncate),
|
||||
newLabelWithWrapping("artist", fyne.TextTruncate),
|
||||
newLabelWithWrapping("source", fyne.TextTruncate)))
|
||||
component.NewLabelWithOpts("title", component.LabelTruncation(fyne.TextTruncateClip)),
|
||||
component.NewLabelWithOpts("artist", component.LabelTruncation(fyne.TextTruncateClip)),
|
||||
component.NewLabelWithOpts("user", component.LabelTruncation(fyne.TextTruncateClip))))
|
||||
},
|
||||
func(id widget.ListItemID, object fyne.CanvasObject) {
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*widget.Label).SetText(
|
||||
@@ -51,19 +53,19 @@ func createSearchList() fyne.CanvasObject {
|
||||
object.(*fyne.Container).Objects[1].(*widget.Label).SetText(fmt.Sprintf("%d", id))
|
||||
btns := object.(*fyne.Container).Objects[2].(*fyne.Container).Objects
|
||||
btns[0].(*widget.Button).OnTapped = func() {
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.PlayerPlayCmd, events.PlayerPlayCmdEvent{
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.PlayerPlayCmd, events.PlayerPlayCmdEvent{
|
||||
Media: SearchResult.Items[id],
|
||||
})
|
||||
}
|
||||
btns[1].(*widget.Button).OnTapped = func() {
|
||||
_ = global.EventBus.PublishToChannel(eventChannel, events.PlaylistInsertCmd(model.PlaylistIDPlayer), events.PlaylistInsertCmdEvent{
|
||||
_ = global.EventBus.PublishToChannel(gctx.EventChannel, events.PlaylistInsertCmd(model.PlaylistIDPlayer), events.PlaylistInsertCmdEvent{
|
||||
Media: SearchResult.Items[id],
|
||||
Position: -1,
|
||||
})
|
||||
}
|
||||
})
|
||||
global.EventBus.Subscribe(eventChannel, events.SearchResultUpdate, "gui.search.update_result", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
items := event.Data.(events.SearchResultUpdateEvent).Medias
|
||||
global.EventBus.Subscribe(gctx.EventChannel, events.ReplyMiaosicSearch, "gui.search.update_result", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
items := event.Data.(events.ReplyMiaosicSearchData).Medias
|
||||
SearchResult.Items = items
|
||||
SearchResult.mux.Lock()
|
||||
SearchResult.List.Refresh()
|
||||
@@ -1,24 +1,23 @@
|
||||
package gui
|
||||
package systray
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/gui/gctx"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"AynaLivePlayer/resource"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/driver/desktop"
|
||||
)
|
||||
|
||||
func setupSysTray() {
|
||||
if desk, ok := App.(desktop.App); ok {
|
||||
func SetupSysTray() {
|
||||
if desk, ok := gctx.Context.App.(desktop.App); ok {
|
||||
m := fyne.NewMenu("MyApp",
|
||||
fyne.NewMenuItem(i18n.T("gui.tray.btn.show"), func() {
|
||||
MainWindow.Show()
|
||||
gctx.Context.Window.Show()
|
||||
}))
|
||||
desk.SetSystemTrayMenu(m)
|
||||
desk.SetSystemTrayIcon(resource.ImageIcon)
|
||||
}
|
||||
MainWindow.SetCloseIntercept(func() {
|
||||
_ = config.SaveToConfigFile(config.ConfigPath)
|
||||
MainWindow.Hide()
|
||||
gctx.Context.Window.SetCloseIntercept(func() {
|
||||
gctx.Context.Window.Hide()
|
||||
})
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package gui
|
||||
package updater
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/gctx"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
@@ -10,8 +11,8 @@ import (
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
func checkUpdate() {
|
||||
global.EventBus.Subscribe(eventChannel,
|
||||
func CreateUpdaterPopUp() {
|
||||
global.EventBus.Subscribe(gctx.EventChannel,
|
||||
events.CheckUpdateResultUpdate, "gui.updater.check_update", gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
data := event.Data.(events.CheckUpdateResultUpdateEvent)
|
||||
msg := data.Info.Version.String() + "\n\n\n" + data.Info.Info
|
||||
@@ -20,13 +21,13 @@ func checkUpdate() {
|
||||
i18n.T("gui.update.new_version"),
|
||||
"OK",
|
||||
widget.NewRichTextFromMarkdown(msg),
|
||||
MainWindow)
|
||||
gctx.Context.Window)
|
||||
} else {
|
||||
dialog.ShowCustom(
|
||||
i18n.T("gui.update.already_latest_version"),
|
||||
"OK",
|
||||
widget.NewRichTextFromMarkdown(""),
|
||||
MainWindow)
|
||||
gctx.Context.Window)
|
||||
}
|
||||
}))
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
liveroomsdk "github.com/AynaLivePlayer/liveroom-sdk"
|
||||
"github.com/AynaLivePlayer/liveroom-sdk/provider/openblive"
|
||||
"github.com/AynaLivePlayer/liveroom-sdk/provider/webdm"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type liveroom struct {
|
||||
@@ -101,8 +102,8 @@ func addLiveRoom(roomModel model.LiveRoom) {
|
||||
|
||||
func registerHandlers() {
|
||||
global.EventBus.Subscribe("",
|
||||
events.LiveRoomAddCmd, "internal.liveroom.add", func(event *eventbus.Event) {
|
||||
data := event.Data.(events.LiveRoomAddCmdEvent)
|
||||
events.CmdLiveRoomAdd, "internal.liveroom.add", func(event *eventbus.Event) {
|
||||
data := event.Data.(events.CmdLiveRoomAddData)
|
||||
addLiveRoom(model.LiveRoom{
|
||||
LiveRoom: liveroomsdk.LiveRoom{
|
||||
Provider: data.Provider,
|
||||
@@ -117,8 +118,8 @@ func registerHandlers() {
|
||||
})
|
||||
|
||||
global.EventBus.Subscribe("",
|
||||
events.LiveRoomRemoveCmd, "internal.liveroom.remove", func(event *eventbus.Event) {
|
||||
data := event.Data.(events.LiveRoomRemoveCmdEvent)
|
||||
events.CmdLiveRoomRemove, "internal.liveroom.remove", func(event *eventbus.Event) {
|
||||
data := event.Data.(events.CmdLiveRoomRemoveData)
|
||||
room, ok := liveRooms[data.Identifier]
|
||||
if !ok {
|
||||
log.Errorf("remove room failed, room %s not found", data.Identifier)
|
||||
@@ -134,8 +135,8 @@ func registerHandlers() {
|
||||
})
|
||||
|
||||
global.EventBus.Subscribe("",
|
||||
events.LiveRoomConfigChangeCmd, "internal.liveroom.config.change", func(event *eventbus.Event) {
|
||||
data := event.Data.(events.LiveRoomConfigChangeCmdEvent)
|
||||
events.CmdLiveRoomConfigChange, "internal.liveroom.config.change", func(event *eventbus.Event) {
|
||||
data := event.Data.(events.CmdLiveRoomConfigChangeData)
|
||||
if room, ok := liveRooms[data.Identifier]; ok {
|
||||
room.model.Config = data.Config
|
||||
sendRoomStatusUpdateEvent(data.Identifier)
|
||||
@@ -143,8 +144,8 @@ func registerHandlers() {
|
||||
})
|
||||
|
||||
global.EventBus.Subscribe("",
|
||||
events.LiveRoomOperationCmd, "internal.liveroom.operation", func(event *eventbus.Event) {
|
||||
data := event.Data.(events.LiveRoomOperationCmdEvent)
|
||||
events.CmdLiveRoomOperation, "internal.liveroom.operation", func(event *eventbus.Event) {
|
||||
data := event.Data.(events.CmdLiveRoomOperationData)
|
||||
log.Infof("Live room operation SetConnect %v", data.SetConnect)
|
||||
room, ok := liveRooms[data.Identifier]
|
||||
if !ok {
|
||||
@@ -164,8 +165,8 @@ func registerHandlers() {
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
_ = global.EventBus.Publish(
|
||||
events.LiveRoomOperationFinish, events.LiveRoomOperationFinishEvent{})
|
||||
_ = global.EventBus.Reply(event,
|
||||
events.ReplyLiveRoomOperation, events.ReplyLiveRoomOperationData{})
|
||||
sendRoomStatusUpdateEvent(data.Identifier)
|
||||
})
|
||||
}
|
||||
@@ -178,8 +179,8 @@ func sendRoomStatusUpdateEvent(roomId string) {
|
||||
}
|
||||
log.Infof("send room status update event, room %s", roomId)
|
||||
_ = global.EventBus.Publish(
|
||||
events.LiveRoomStatusUpdate,
|
||||
events.LiveRoomStatusUpdateEvent{
|
||||
events.UpdateLiveRoomStatus,
|
||||
events.UpdateLiveRoomStatusData{
|
||||
Room: room.model,
|
||||
})
|
||||
}
|
||||
@@ -189,9 +190,12 @@ func sendRoomsUpdateEvent() {
|
||||
for _, r := range liveRooms {
|
||||
rooms = append(rooms, r.model)
|
||||
}
|
||||
sort.Slice(rooms, func(i, j int) bool {
|
||||
return rooms[i].LiveRoom.Identifier() < rooms[j].LiveRoom.Identifier()
|
||||
})
|
||||
_ = global.EventBus.Publish(
|
||||
events.LiveRoomRoomsUpdate,
|
||||
events.LiveRoomRoomsUpdateEvent{
|
||||
events.UpdateLiveRoomRooms,
|
||||
events.UpdateLiveRoomRoomsData{
|
||||
Rooms: rooms,
|
||||
})
|
||||
}
|
||||
@@ -218,8 +222,8 @@ func callEvents() {
|
||||
for _, r := range liveRooms {
|
||||
if r.model.Config.AutoConnect {
|
||||
_ = global.EventBus.Publish(
|
||||
events.LiveRoomOperationCmd,
|
||||
events.LiveRoomOperationCmdEvent{
|
||||
events.CmdLiveRoomOperation,
|
||||
events.CmdLiveRoomOperationData{
|
||||
Identifier: r.room.Config().Identifier(),
|
||||
SetConnect: true,
|
||||
})
|
||||
|
||||
@@ -199,16 +199,20 @@ func registerCmdHandler() {
|
||||
})
|
||||
mediaInfo := evnt.Data.(events.PlayerPlayCmdEvent).Media.Info
|
||||
media := evnt.Data.(events.PlayerPlayCmdEvent).Media
|
||||
if m, err := miaosic.GetMediaInfo(media.Info.Meta); err == nil {
|
||||
media.Info = m
|
||||
resp, err := global.EventBus.Call(events.CmdMiaosicGetMediaInfo, events.ReplyMiaosicGetMediaInfo,
|
||||
events.CmdMiaosicGetMediaInfoData{Meta: media.Info.Meta})
|
||||
if err == nil && resp.Data.(events.ReplyMiaosicGetMediaInfoData).Error == nil {
|
||||
media.Info = resp.Data.(events.ReplyMiaosicGetMediaInfoData).Info
|
||||
}
|
||||
_ = global.EventBus.Publish(events.PlayerPlayingUpdate, events.PlayerPlayingUpdateEvent{
|
||||
Media: media,
|
||||
Removed: false,
|
||||
})
|
||||
log.Infof("[MPV Player] Play media %s", mediaInfo.Title)
|
||||
mediaUrls, err := miaosic.GetMediaUrl(mediaInfo.Meta, miaosic.QualityAny)
|
||||
if err != nil || len(mediaUrls) == 0 {
|
||||
resp, err = global.EventBus.Call(events.CmdMiaosicGetMediaUrl, events.ReplyMiaosicGetMediaUrl,
|
||||
events.CmdMiaosicGetMediaUrlData{Meta: media.Info.Meta, Quality: miaosic.QualityAny})
|
||||
mediaUrls := resp.Data.(events.ReplyMiaosicGetMediaUrlData)
|
||||
if err != nil || mediaUrls.Error != nil || len(mediaUrls.Urls) == 0 {
|
||||
log.Warn("[MPV PlayControl] get media url failed ", mediaInfo.Meta.ID(), err)
|
||||
if err := libmpv.Command([]string{"stop"}); err != nil {
|
||||
log.Error("[MPV PlayControl] failed to stop", err)
|
||||
@@ -220,7 +224,7 @@ func registerCmdHandler() {
|
||||
})
|
||||
return
|
||||
}
|
||||
mediaUrl := mediaUrls[0]
|
||||
mediaUrl := mediaUrls.Urls[0]
|
||||
if val, ok := mediaUrl.Header["User-Agent"]; ok {
|
||||
log.Debug("[MPV PlayControl] set user-agent for mpv player")
|
||||
err := libmpv.SetPropertyString("user-agent", val)
|
||||
|
||||
@@ -5,13 +5,11 @@ import (
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
"AynaLivePlayer/pkg/logger"
|
||||
"github.com/AynaLivePlayer/miaosic"
|
||||
)
|
||||
|
||||
var PlayerPlaylist *playlist = nil
|
||||
var HistoryPlaylist *playlist = nil
|
||||
var SystemPlaylist *playlist = nil
|
||||
var PlaylistsPlaylist *playlist = nil
|
||||
|
||||
@@ -53,7 +51,6 @@ func Initialize() {
|
||||
log = global.Logger.WithPrefix("Playlists")
|
||||
PlayerPlaylist = newPlaylist(model.PlaylistIDPlayer)
|
||||
SystemPlaylist = newPlaylist(model.PlaylistIDSystem)
|
||||
HistoryPlaylist = newPlaylist(model.PlaylistIDHistory)
|
||||
config.LoadConfig(cfg)
|
||||
|
||||
_ = global.EventBus.Publish(events.PlaylistModeChangeCmd(model.PlaylistIDPlayer), events.PlaylistModeChangeCmdEvent{
|
||||
@@ -64,19 +61,6 @@ func Initialize() {
|
||||
Mode: cfg.SystemPlaylistMode,
|
||||
})
|
||||
|
||||
global.EventBus.Subscribe("",
|
||||
events.PlayerPlayingUpdate,
|
||||
"internal.playlist.player_playing_update",
|
||||
func(event *eventbus.Event) {
|
||||
if event.Data.(events.PlayerPlayingUpdateEvent).Removed {
|
||||
return
|
||||
}
|
||||
_ = global.EventBus.Publish(events.PlaylistInsertCmd(model.PlaylistIDHistory), events.PlaylistInsertCmdEvent{
|
||||
Media: event.Data.(events.PlayerPlayingUpdateEvent).Media,
|
||||
Position: -1,
|
||||
})
|
||||
})
|
||||
|
||||
createPlaylistManager()
|
||||
}
|
||||
|
||||
|
||||
@@ -95,6 +95,7 @@ func createPlaylistManager() {
|
||||
})
|
||||
return
|
||||
}
|
||||
// todo: use eventbus instead
|
||||
getPlaylist, err := miaosic.GetPlaylist(pl.Meta)
|
||||
if err != nil {
|
||||
_ = global.EventBus.Publish(
|
||||
@@ -156,6 +157,7 @@ func createPlaylistManager() {
|
||||
})
|
||||
return
|
||||
}
|
||||
// todo: use eventbus instead
|
||||
pl, err := miaosic.GetPlaylist(meta)
|
||||
if err != nil {
|
||||
_ = global.EventBus.Publish(
|
||||
|
||||
@@ -37,6 +37,7 @@ func Initialize() {
|
||||
|
||||
loadMediaProvider()
|
||||
handleSearch()
|
||||
handleInfo()
|
||||
createLyricLoader()
|
||||
|
||||
_ = global.EventBus.Publish(
|
||||
|
||||
39
internal/source/info.go
Normal file
39
internal/source/info.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package source
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
"github.com/AynaLivePlayer/miaosic"
|
||||
)
|
||||
|
||||
func handleInfo() {
|
||||
err := global.EventBus.Subscribe("",
|
||||
events.CmdMiaosicGetMediaInfo, "internal.media_provider.getMediaInfo", func(event *eventbus.Event) {
|
||||
info, err := miaosic.GetMediaInfo(event.Data.(events.CmdMiaosicGetMediaInfoData).Meta)
|
||||
_ = global.EventBus.Reply(
|
||||
event, events.ReplyMiaosicGetMediaInfo,
|
||||
events.ReplyMiaosicGetMediaInfoData{
|
||||
Info: info,
|
||||
Error: err,
|
||||
},
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
log.ErrorW("Subscribe search event failed", "error", err)
|
||||
}
|
||||
err = global.EventBus.Subscribe("",
|
||||
events.CmdMiaosicGetMediaUrl, "internal.media_provider.getMediaUrl", func(event *eventbus.Event) {
|
||||
urls, err := miaosic.GetMediaUrl(event.Data.(events.CmdMiaosicGetMediaUrlData).Meta, event.Data.(events.CmdMiaosicGetMediaUrlData).Quality)
|
||||
_ = global.EventBus.Reply(
|
||||
event, events.ReplyMiaosicGetMediaUrl,
|
||||
events.ReplyMiaosicGetMediaUrlData{
|
||||
Urls: urls,
|
||||
Error: err,
|
||||
},
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
log.ErrorW("Subscribe search event failed", "error", err)
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
|
||||
func handleSearch() {
|
||||
err := global.EventBus.Subscribe("",
|
||||
events.SearchCmd, "internal.media_provider.search_handler", func(event *eventbus.Event) {
|
||||
data := event.Data.(events.SearchCmdEvent)
|
||||
events.CmdMiaosicSearch, "internal.media_provider.search_handler", func(event *eventbus.Event) {
|
||||
data := event.Data.(events.CmdMiaosicSearchData)
|
||||
log.Infof("Search %s using %s", data.Keyword, data.Provider)
|
||||
searchResult, err := miaosic.SearchByProvider(data.Provider, data.Keyword, 1, 10)
|
||||
if err != nil {
|
||||
@@ -26,8 +26,8 @@ func handleSearch() {
|
||||
}
|
||||
}
|
||||
_ = global.EventBus.Reply(
|
||||
event, events.SearchResultUpdate,
|
||||
events.SearchResultUpdateEvent{
|
||||
event, events.ReplyMiaosicSearch,
|
||||
events.ReplyMiaosicSearchData{
|
||||
Medias: medias,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -16,8 +16,10 @@ func loadMediaProvider() {
|
||||
kugou.UseInstrumental()
|
||||
miaosic.RegisterProvider(local.NewLocal(sourceCfg.LocalSourcePath))
|
||||
if sourceCfg.QQChannel == "wechat" {
|
||||
log.Info("qqmusic: using wechat login channel")
|
||||
qq.UseWechatLogin()
|
||||
} else {
|
||||
log.Infof("qqmusic: using qq login channel")
|
||||
qq.UseQQLogin()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,18 +152,13 @@ func (b *bus) Wait() error {
|
||||
select {
|
||||
case <-done:
|
||||
return nil
|
||||
case <-b.drainedCh:
|
||||
// Stopped
|
||||
<-done
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *bus) Stop() error {
|
||||
b.stopOnce.Do(func() {
|
||||
b.stopping.Store(true)
|
||||
close(b.stopCh) // signal workers to stop immediately
|
||||
close(b.drainedCh) // allow Wait() to proceed
|
||||
close(b.stopCh) // signal workers to stop immediately
|
||||
})
|
||||
return nil
|
||||
}
|
||||
@@ -328,7 +323,7 @@ func (b *bus) Call(eventId string, subEvtId string, data interface{}) (*Event, e
|
||||
return resp, nil
|
||||
case <-timeout:
|
||||
return nil, errors.New("call timeout")
|
||||
case <-b.drainedCh:
|
||||
case <-b.stopCh:
|
||||
return nil, errors.New("bus stopped")
|
||||
}
|
||||
}
|
||||
|
||||
Submodule pkg/miaosic updated: f0d0cdb6dd...f27d4084c9
@@ -4,8 +4,8 @@ import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui"
|
||||
"AynaLivePlayer/gui/component"
|
||||
config2 "AynaLivePlayer/gui/views/config"
|
||||
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
@@ -129,8 +129,8 @@ func (c *Diange) OnSave() {
|
||||
|
||||
func (d *Diange) Enable() error {
|
||||
config.LoadConfig(d)
|
||||
gui.AddConfigLayout(d)
|
||||
gui.AddConfigLayout(&blacklist{})
|
||||
config2.AddConfigLayout(d)
|
||||
config2.AddConfigLayout(&blacklist{})
|
||||
global.EventBus.Subscribe("",
|
||||
events.LiveRoomMessageReceive,
|
||||
"plugin.diange.message",
|
||||
|
||||
@@ -3,7 +3,7 @@ package durationmgmt
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui"
|
||||
config2 "AynaLivePlayer/gui/views/config"
|
||||
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
@@ -42,7 +42,7 @@ func (d *MaxDuration) Name() string {
|
||||
|
||||
func (d *MaxDuration) Enable() error {
|
||||
config.LoadConfig(d)
|
||||
gui.AddConfigLayout(d)
|
||||
config2.AddConfigLayout(d)
|
||||
global.EventBus.Subscribe("",
|
||||
events.PlayerPropertyDurationUpdate,
|
||||
"plugin.maxduration.duration",
|
||||
|
||||
@@ -3,8 +3,8 @@ package qiege
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui"
|
||||
"AynaLivePlayer/gui/component"
|
||||
config2 "AynaLivePlayer/gui/views/config"
|
||||
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
@@ -44,7 +44,7 @@ func (d *Qiege) Name() string {
|
||||
|
||||
func (d *Qiege) Enable() error {
|
||||
config.LoadConfig(d)
|
||||
gui.AddConfigLayout(d)
|
||||
config2.AddConfigLayout(d)
|
||||
global.EventBus.Subscribe("",
|
||||
events.LiveRoomMessageReceive,
|
||||
"plugin.qiege.message",
|
||||
|
||||
@@ -3,8 +3,8 @@ package sourcelogin
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui"
|
||||
"AynaLivePlayer/gui/component"
|
||||
config2 "AynaLivePlayer/gui/views/config"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"AynaLivePlayer/pkg/logger"
|
||||
@@ -49,7 +49,7 @@ func (w *SourceLogin) Name() string {
|
||||
|
||||
func (w *SourceLogin) Enable() error {
|
||||
config.LoadConfig(w)
|
||||
gui.AddConfigLayout(w)
|
||||
config2.AddConfigLayout(w)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui"
|
||||
"AynaLivePlayer/gui/component"
|
||||
config2 "AynaLivePlayer/gui/views/config"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
@@ -92,7 +92,7 @@ func (t *TextInfo) Enable() (err error) {
|
||||
config.LoadConfig(t)
|
||||
t.reloadTemplates()
|
||||
t.registerHandlers()
|
||||
gui.AddConfigLayout(t)
|
||||
config2.AddConfigLayout(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ package wshub
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui"
|
||||
"AynaLivePlayer/gui/component"
|
||||
config2 "AynaLivePlayer/gui/views/config"
|
||||
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
@@ -46,7 +46,7 @@ func (w *WsHub) Enable() error {
|
||||
// todo: should pass EnableWsHubControl to client instead of using global variable
|
||||
globalEnableWsHubControl = w.EnableWsHubControl
|
||||
w.server = newWsServer(&w.Port, &w.LocalHostOnly)
|
||||
gui.AddConfigLayout(w)
|
||||
config2.AddConfigLayout(w)
|
||||
w.registerEvents()
|
||||
w.log.Info("webinfo loaded")
|
||||
if w.Enabled {
|
||||
|
||||
@@ -3,8 +3,8 @@ package yinliang
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui"
|
||||
"AynaLivePlayer/gui/component"
|
||||
config2 "AynaLivePlayer/gui/views/config"
|
||||
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
@@ -65,7 +65,7 @@ func (y *Yinliang) Enable() error {
|
||||
y.MaxVolume = 0
|
||||
}
|
||||
|
||||
gui.AddConfigLayout(y)
|
||||
config2.AddConfigLayout(y)
|
||||
|
||||
_ = global.EventBus.Subscribe("",
|
||||
events.LiveRoomMessageReceive,
|
||||
|
||||
Reference in New Issue
Block a user