From c47d338a9ebeabd2c59c675c0c609d75a4cbc4fb Mon Sep 17 00:00:00 2001 From: Aynakeya Date: Fri, 23 Dec 2022 05:06:57 -0800 Subject: [PATCH] rewrite using IoC and DI --- app/cli/main.go | 23 -- app/gui/main.go | 32 ++- common/event/event.go | 115 ++++++++ common/event/pool.go | 10 + {i18n => common/i18n}/i18n.go | 2 +- {logger => common/logger}/logger.go | 0 {util => common/util}/hash.go | 0 {util => common/util}/image.go | 0 {util => common/util}/integer.go | 0 {util => common/util}/json.go | 0 {util => common/util}/string.go | 0 {util => common/util}/time.go | 0 {util => common/util}/time_test.go | 0 {util => common/util}/url.go | 0 config/config.go | 4 +- config/config_player.go | 55 ---- config/config_provider.go | 16 -- controller/command.go | 31 --- controller/controller.go | 16 +- controller/controller_test.go | 10 - controller/core/controller.go | 59 +++++ controller/core/liveroom.go | 201 ++++++++++++++ controller/core/lyric.go | 56 ++++ controller/core/playcontrol.go | 242 +++++++++++++++++ controller/core/playlist.go | 397 ++++++++++++++++++++++++++++ controller/core/plugin.go | 44 +++ controller/core/provider.go | 114 ++++++++ controller/danmu.go | 23 -- controller/global.go | 76 ------ controller/handlers.go | 35 --- controller/liveroom.go | 178 ++----------- controller/playcontrol.go | 34 +++ controller/player.go | 130 --------- controller/playlist.go | 122 ++------- controller/plugin.go | 25 +- controller/provider.go | 99 ++----- event/event.go | 88 ------ go.mod | 2 +- go.sum | 2 + gui/config_basic.go | 43 ++- gui/gui.go | 4 +- gui/helper.go | 17 +- gui/history_list.go | 26 +- gui/player_controller.go | 147 +++++----- gui/player_lyric.go | 54 ++-- gui/player_playlist.go | 38 +-- gui/playlist_list.go | 33 ++- gui/room_bar.go | 32 +-- gui/search_bar.go | 17 +- gui/search_list.go | 15 +- liveclient/bilibili.go | 30 +-- liveclient/bilibili_test.go | 8 +- liveclient/event.go | 2 +- liveclient/liveclient.go | 5 +- {player => model}/event.go | 26 +- model/liveroom.go | 17 ++ {player => model}/lyric.go | 37 +-- {player => model}/lyric_test.go | 8 +- {player => model}/media.go | 2 +- {player => model}/media_test.go | 2 +- model/player.go | 18 ++ model/playlist.go | 36 +++ model/provider.go | 6 + model/user.go | 5 + player/player.go | 197 ++------------ player/player_mpv.go | 204 ++++++++++++++ player/player_test.go | 3 +- player/playlist.go | 196 -------------- player/playlist_test.go | 22 -- player/user.go | 8 - plugin/diange/diange.go | 28 +- plugin/qiege/qiege.go | 20 +- plugin/textinfo/textinfo.go | 116 ++++---- plugin/webinfo/info.go | 8 +- plugin/webinfo/template.go | 2 +- plugin/webinfo/webinfo.go | 139 +++++----- plugin/wylogin/wylogin.go | 4 +- provider/bilibili.go | 34 +-- provider/bilibili_test.go | 10 +- provider/bilivideo.go | 38 +-- provider/bilivideo_test.go | 18 +- provider/kuwo.go | 46 ++-- provider/kuwo_test.go | 16 +- provider/local.go | 47 ++-- provider/local_helper.go | 23 +- provider/netease.go | 48 ++-- provider/netease_test.go | 16 +- provider/provider.go | 39 ++- 88 files changed, 2295 insertions(+), 1856 deletions(-) delete mode 100644 app/cli/main.go create mode 100644 common/event/event.go create mode 100644 common/event/pool.go rename {i18n => common/i18n}/i18n.go (97%) rename {logger => common/logger}/logger.go (100%) rename {util => common/util}/hash.go (100%) rename {util => common/util}/image.go (100%) rename {util => common/util}/integer.go (100%) rename {util => common/util}/json.go (100%) rename {util => common/util}/string.go (100%) rename {util => common/util}/time.go (100%) rename {util => common/util}/time_test.go (100%) rename {util => common/util}/url.go (100%) delete mode 100644 config/config_player.go delete mode 100644 config/config_provider.go delete mode 100644 controller/command.go delete mode 100644 controller/controller_test.go create mode 100644 controller/core/controller.go create mode 100644 controller/core/liveroom.go create mode 100644 controller/core/lyric.go create mode 100644 controller/core/playcontrol.go create mode 100644 controller/core/playlist.go create mode 100644 controller/core/plugin.go create mode 100644 controller/core/provider.go delete mode 100644 controller/danmu.go delete mode 100644 controller/global.go delete mode 100644 controller/handlers.go create mode 100644 controller/playcontrol.go delete mode 100644 controller/player.go delete mode 100644 event/event.go rename {player => model}/event.go (61%) create mode 100644 model/liveroom.go rename {player => model}/lyric.go (72%) rename {player => model}/lyric_test.go (93%) rename {player => model}/media.go (98%) rename {player => model}/media_test.go (96%) create mode 100644 model/player.go create mode 100644 model/playlist.go create mode 100644 model/provider.go create mode 100644 model/user.go create mode 100644 player/player_mpv.go delete mode 100644 player/playlist.go delete mode 100644 player/playlist_test.go delete mode 100644 player/user.go diff --git a/app/cli/main.go b/app/cli/main.go deleted file mode 100644 index f25f8fd..0000000 --- a/app/cli/main.go +++ /dev/null @@ -1,23 +0,0 @@ -package main - -import ( - "AynaLivePlayer/config" - "AynaLivePlayer/controller" - "AynaLivePlayer/logger" - "fmt" - "github.com/sirupsen/logrus" -) - -func main() { - fmt.Printf("BiliAudioBot Revive %s\n", config.Version) - logger.Logger.SetLevel(logrus.DebugLevel) - fmt.Println("Please enter room id") - var roomid string - - // Taking input from user - fmt.Scanln(&roomid) - controller.Initialize() - controller.SetDanmuClient(roomid) - ch := make(chan int) - <-ch -} diff --git a/app/gui/main.go b/app/gui/main.go index d09448f..39a7fac 100644 --- a/app/gui/main.go +++ b/app/gui/main.go @@ -1,11 +1,13 @@ package main import ( + "AynaLivePlayer/common/i18n" + "AynaLivePlayer/common/logger" "AynaLivePlayer/config" "AynaLivePlayer/controller" + "AynaLivePlayer/controller/core" "AynaLivePlayer/gui" - "AynaLivePlayer/i18n" - "AynaLivePlayer/logger" + "AynaLivePlayer/player" "AynaLivePlayer/plugin/diange" "AynaLivePlayer/plugin/qiege" "AynaLivePlayer/plugin/textinfo" @@ -16,22 +18,34 @@ import ( var dev = flag.Bool("dev", false, "generate new translation file") -var plugins = []controller.Plugin{diange.NewDiange(), qiege.NewQiege(), textinfo.NewTextInfo(), webinfo.NewWebInfo(), - wylogin.NewWYLogin()} +func createController() controller.IController { + liveroom := core.NewLiveRoomController() + lyric := core.NewLyricLoader() + provider := core.NewProviderController() + playlist := core.NewPlaylistController(provider) + plugin := core.NewPluginController() + mpvPlayer := player.NewMpvPlayer() + playControl := core.NewPlayerController(mpvPlayer, playlist, lyric, provider) + ctr := core.NewController(liveroom, playControl, playlist, provider, plugin) + return ctr +} func main() { flag.Parse() logger.Logger.Info("================Program Start================") logger.Logger.Infof("================Current Version: %s================", config.Version) - controller.Initialize() + mainController := createController() + controller.Instance = mainController gui.Initialize() - controller.LoadPlugins(plugins...) + plugins := []controller.Plugin{diange.NewDiange(mainController), qiege.NewQiege(mainController), + textinfo.NewTextInfo(mainController), webinfo.NewWebInfo(mainController), + wylogin.NewWYLogin()} + mainController.LoadPlugins(plugins...) gui.MainWindow.ShowAndRun() - controller.ClosePlugins(plugins...) - controller.Destroy() - controller.CloseAndSave() + mainController.CloseAndSave() if *dev { i18n.SaveTranslation() } + _ = config.SaveToConfigFile(config.ConfigPath) logger.Logger.Info("================Program End================") } diff --git a/common/event/event.go b/common/event/event.go new file mode 100644 index 0000000..6291bc6 --- /dev/null +++ b/common/event/event.go @@ -0,0 +1,115 @@ +package event + +import ( + "sync" +) + +type EventId string + +type Event struct { + Id EventId + Cancelled bool + Data interface{} +} + +type HandlerFunc func(event *Event) + +type Handler struct { + EventId EventId + Name string + Handler HandlerFunc +} + +type Manager struct { + handlers map[string]*Handler + queue chan func() + stopSig chan int + queueSize int + workerSize int + lock sync.RWMutex +} + +func NewManger(queueSize int, workerSize int) *Manager { + manager := &Manager{ + handlers: make(map[string]*Handler), + queue: make(chan func(), queueSize), + stopSig: make(chan int, workerSize), + queueSize: queueSize, + workerSize: workerSize, + } + for i := 0; i < workerSize; i++ { + go func() { + for { + select { + case <-manager.stopSig: + return + case f := <-manager.queue: + f() + } + } + }() + } + return manager +} + +func (h *Manager) NewChildManager() *Manager { + return &Manager{ + handlers: make(map[string]*Handler), + queue: h.queue, + stopSig: h.stopSig, + queueSize: h.queueSize, + workerSize: h.workerSize, + } +} + +func (h *Manager) Stop() { + for i := 0; i < h.workerSize; i++ { + h.stopSig <- 0 + } +} + +func (h *Manager) Register(handler *Handler) { + h.lock.Lock() + defer h.lock.Unlock() + h.handlers[handler.Name] = handler +} + +func (h *Manager) RegisterA(id EventId, name string, handler HandlerFunc) { + h.Register(&Handler{ + EventId: id, + Name: name, + Handler: handler, + }) +} + +func (h *Manager) UnregisterAll() { + h.lock.Lock() + defer h.lock.Unlock() + h.handlers = make(map[string]*Handler) +} + +func (h *Manager) Unregister(name string) { + h.lock.Lock() + defer h.lock.Unlock() + delete(h.handlers, name) +} + +func (h *Manager) Call(event *Event) { + h.lock.RLock() + defer h.lock.RUnlock() + for _, eh := range h.handlers { + if eh.EventId == event.Id { + handler := eh.Handler + h.queue <- func() { + handler(event) + } + } + } +} + +func (h *Manager) CallA(id EventId, data interface{}) { + h.Call(&Event{ + Id: id, + Data: data, + }) +} diff --git a/common/event/pool.go b/common/event/pool.go new file mode 100644 index 0000000..67a8e73 --- /dev/null +++ b/common/event/pool.go @@ -0,0 +1,10 @@ +package event + +var MAX_QUEUE_SIZE = 128 +var MAX_WORKER_SIZE = 16 + +var MainManager *Manager + +func init() { + MainManager = NewManger(MAX_QUEUE_SIZE, MAX_WORKER_SIZE) +} diff --git a/i18n/i18n.go b/common/i18n/i18n.go similarity index 97% rename from i18n/i18n.go rename to common/i18n/i18n.go index 1755a57..b90ec14 100644 --- a/i18n/i18n.go +++ b/common/i18n/i18n.go @@ -1,8 +1,8 @@ package i18n import ( + "AynaLivePlayer/common/util" "AynaLivePlayer/config" - "AynaLivePlayer/util" "encoding/json" "os" ) diff --git a/logger/logger.go b/common/logger/logger.go similarity index 100% rename from logger/logger.go rename to common/logger/logger.go diff --git a/util/hash.go b/common/util/hash.go similarity index 100% rename from util/hash.go rename to common/util/hash.go diff --git a/util/image.go b/common/util/image.go similarity index 100% rename from util/image.go rename to common/util/image.go diff --git a/util/integer.go b/common/util/integer.go similarity index 100% rename from util/integer.go rename to common/util/integer.go diff --git a/util/json.go b/common/util/json.go similarity index 100% rename from util/json.go rename to common/util/json.go diff --git a/util/string.go b/common/util/string.go similarity index 100% rename from util/string.go rename to common/util/string.go diff --git a/util/time.go b/common/util/time.go similarity index 100% rename from util/time.go rename to common/util/time.go diff --git a/util/time_test.go b/common/util/time_test.go similarity index 100% rename from util/time_test.go rename to common/util/time_test.go diff --git a/util/url.go b/common/util/url.go similarity index 100% rename from util/url.go rename to common/util/url.go diff --git a/config/config.go b/config/config.go index 09fc29d..d0da19f 100644 --- a/config/config.go +++ b/config/config.go @@ -8,7 +8,7 @@ import ( const ( ProgramName = "卡西米尔唱片机" - Version = "beta 0.9.3" + Version = "beta 0.9.5" ) const ( @@ -55,7 +55,7 @@ func init() { fmt.Println("config not found, using default config") ConfigFile = ini.Empty() } - for _, cfg := range []Config{Log, Player, Provider, General} { + for _, cfg := range []Config{Log, General} { LoadConfig(cfg) } } diff --git a/config/config_player.go b/config/config_player.go deleted file mode 100644 index c7984dd..0000000 --- a/config/config_player.go +++ /dev/null @@ -1,55 +0,0 @@ -package config - -type _PlayerConfig struct { - PlaylistData string - Playlists []*PlayerPlaylist `ini:"-"` - //PlaylistsProvider []string - PlaylistIndex int - PlaylistRandom bool - UserPlaylistRandom bool - AudioDevice string - Volume float64 - SkipPlaylist bool -} - -type PlayerPlaylist struct { - ID string - Provider string -} - -func (c *_PlayerConfig) Name() string { - return "Player" -} - -func (c *_PlayerConfig) OnLoad() { - //c.Playlists = make([]*PlayerPlaylist, 0) - _ = LoadJson(c.PlaylistData, &c.Playlists) -} - -func (c *_PlayerConfig) OnSave() { - _ = SaveJson(c.PlaylistData, &c.Playlists) -} - -var Player = &_PlayerConfig{ - PlaylistData: "playlists.json", - Playlists: []*PlayerPlaylist{ - { - "2382819181", - "netease", - }, - { - "4987059624", - "netease", - }, - { - "list1", - "local", - }, - }, - PlaylistIndex: 0, - PlaylistRandom: true, - UserPlaylistRandom: false, - AudioDevice: "auto", - Volume: 100, - SkipPlaylist: false, -} diff --git a/config/config_provider.go b/config/config_provider.go deleted file mode 100644 index fe3d1cd..0000000 --- a/config/config_provider.go +++ /dev/null @@ -1,16 +0,0 @@ -package config - -type _ProviderConfig struct { - BaseConfig - Priority []string - LocalDir string -} - -func (c *_ProviderConfig) Name() string { - return "Provider" -} - -var Provider = &_ProviderConfig{ - Priority: []string{"netease", "kuwo", "bilibili", "local", "bilibili-video"}, - LocalDir: "./music", -} diff --git a/controller/command.go b/controller/command.go deleted file mode 100644 index 8b94c69..0000000 --- a/controller/command.go +++ /dev/null @@ -1,31 +0,0 @@ -package controller - -import ( - "AynaLivePlayer/event" - "AynaLivePlayer/liveclient" - "strings" -) - -var Commands []DanmuCommandExecutor - -type DanmuCommandExecutor interface { - Match(command string) bool - Execute(command string, args []string, danmu *liveclient.DanmuMessage) -} - -func AddCommand(executors ...DanmuCommandExecutor) { - Commands = append(Commands, executors...) -} - -func danmuCommandHandler(event *event.Event) { - danmu := event.Data.(*liveclient.DanmuMessage) - args := strings.Split(danmu.Message, " ") - if len(args[0]) == 0 { - return - } - for _, cmd := range Commands { - if cmd.Match(args[0]) { - cmd.Execute(args[0], args[1:], danmu) - } - } -} diff --git a/controller/controller.go b/controller/controller.go index 846ec71..3c5e158 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -1,9 +1,13 @@ package controller -import ( - "AynaLivePlayer/logger" -) +var Instance IController = nil -const MODULE_CONTROLLER = "Controller" - -var l = logger.Logger.WithField("Module", MODULE_CONTROLLER) +type IController interface { + LiveRooms() ILiveRoomController + PlayControl() IPlayController + Playlists() IPlaylistController + Provider() IProviderController + Plugin() IPluginController + LoadPlugins(plugins ...Plugin) + CloseAndSave() +} diff --git a/controller/controller_test.go b/controller/controller_test.go deleted file mode 100644 index 305b90b..0000000 --- a/controller/controller_test.go +++ /dev/null @@ -1,10 +0,0 @@ -package controller - -import ( - "fmt" - "testing" -) - -func TestController(t *testing.T) { - fmt.Println(LiveClient == nil) -} diff --git a/controller/core/controller.go b/controller/core/controller.go new file mode 100644 index 0000000..d806023 --- /dev/null +++ b/controller/core/controller.go @@ -0,0 +1,59 @@ +package core + +import ( + "AynaLivePlayer/common/logger" + "AynaLivePlayer/controller" +) + +var lg = logger.Logger.WithField("Module", "CoreController") + +type Controller struct { + liveroom controller.ILiveRoomController `ini:"-"` + player controller.IPlayController `ini:"-"` + lyric controller.ILyricLoader `ini:"-"` + playlist controller.IPlaylistController `ini:"-"` + provider controller.IProviderController `ini:"-"` + plugin controller.IPluginController `ini:"-"` +} + +func NewController( + liveroom controller.ILiveRoomController, player controller.IPlayController, + playlist controller.IPlaylistController, + provider controller.IProviderController, plugin controller.IPluginController) controller.IController { + cc := &Controller{ + liveroom: liveroom, + player: player, + playlist: playlist, + provider: provider, + plugin: plugin, + } + return cc +} + +func (c *Controller) LiveRooms() controller.ILiveRoomController { + return c.liveroom +} + +func (c *Controller) PlayControl() controller.IPlayController { + return c.player +} + +func (c *Controller) Playlists() controller.IPlaylistController { + return c.playlist +} + +func (c *Controller) Provider() controller.IProviderController { + return c.provider +} + +func (c *Controller) Plugin() controller.IPluginController { + return c.plugin +} + +func (c *Controller) LoadPlugins(plugins ...controller.Plugin) { + c.plugin.LoadPlugins(plugins...) +} + +func (c *Controller) CloseAndSave() { + c.plugin.ClosePlugins() +} diff --git a/controller/core/liveroom.go b/controller/core/liveroom.go new file mode 100644 index 0000000..50f3892 --- /dev/null +++ b/controller/core/liveroom.go @@ -0,0 +1,201 @@ +package core + +import ( + "AynaLivePlayer/common/event" + "AynaLivePlayer/config" + "AynaLivePlayer/controller" + "AynaLivePlayer/liveclient" + "AynaLivePlayer/model" + "errors" + "strings" +) + +type coreLiveRoom struct { + model.LiveRoom + client liveclient.LiveClient +} + +func (r *coreLiveRoom) Model() *model.LiveRoom { + return &r.LiveRoom +} + +func (r *coreLiveRoom) Status() bool { + return r.client.Status() +} + +func (r *coreLiveRoom) EventManager() *event.Manager { + return r.client.EventManager() +} + +func (r *coreLiveRoom) init(msgHandler event.HandlerFunc) (err error) { + if r.client != nil { + return nil + } + r.client, err = liveclient.NewLiveClient(r.ClientName, r.ID) + if err != nil { + return + } + r.client.EventManager().RegisterA( + liveclient.EventMessageReceive, + "controller.danmu.command", + msgHandler) + return nil +} + +type LiveRoomController struct { + LiveRoomPath string + liveRooms []*coreLiveRoom + danmuCommands []controller.DanmuCommandExecutor +} + +func NewLiveRoomController() controller.ILiveRoomController { + lr := &LiveRoomController{ + LiveRoomPath: "liverooms.json", + liveRooms: []*coreLiveRoom{ + {LiveRoom: model.LiveRoom{ + ClientName: "bilibili", + ID: "9076804", + AutoConnect: false, + }}, + {LiveRoom: model.LiveRoom{ + ClientName: "bilibili", + ID: "3819533", + AutoConnect: false, + }}, + }, + danmuCommands: make([]controller.DanmuCommandExecutor, 0), + } + config.LoadConfig(lr) + lr.initialize() + return lr +} + +func (lr *LiveRoomController) danmuCommandHandler(event *event.Event) { + danmu := event.Data.(*liveclient.DanmuMessage) + args := strings.Split(danmu.Message, " ") + if len(args[0]) == 0 { + return + } + for _, cmd := range lr.danmuCommands { + if cmd.Match(args[0]) { + cmd.Execute(args[0], args[1:], danmu) + } + } +} + +func (lr *LiveRoomController) initialize() { + for i := 0; i < len(lr.liveRooms); i++ { + if lr.liveRooms[i].client == nil { + _ = lr.liveRooms[i].init(lr.danmuCommandHandler) + } + } + go func() { + for i := 0; i < len(lr.liveRooms); i++ { + if lr.liveRooms[i].AutoConnect { + lr.liveRooms[i].client.Connect() + } + } + }() +} + +func (lr *LiveRoomController) Name() string { + return "LiveRooms" +} + +func (lr *LiveRoomController) Size() int { + return len(lr.liveRooms) +} + +func (lr *LiveRoomController) OnLoad() { + rooms := make([]model.LiveRoom, 0) + _ = config.LoadJson(lr.LiveRoomPath, &lr.liveRooms) + if len(rooms) == 0 { + return + } + lr.liveRooms = make([]*coreLiveRoom, len(rooms)) + for i := 0; i < len(rooms); i++ { + lr.liveRooms[i] = &coreLiveRoom{LiveRoom: rooms[i]} + } +} + +func (lr *LiveRoomController) OnSave() { + rooms := make([]model.LiveRoom, len(lr.liveRooms)) + for i := 0; i < len(lr.liveRooms); i++ { + rooms[i] = lr.liveRooms[i].LiveRoom + } + _ = config.SaveJson(lr.LiveRoomPath, &rooms) +} + +func (lr *LiveRoomController) Get(index int) controller.ILiveRoom { + if index < 0 || index >= len(lr.liveRooms) { + return nil + } + return lr.liveRooms[index] +} + +func (lr *LiveRoomController) GetRoomStatus(index int) bool { + if index < 0 || index >= len(lr.liveRooms) { + return false + } + return lr.liveRooms[index].client.Status() +} + +func (lr *LiveRoomController) Connect(index int) error { + lg.Infof("[LiveRooms] Try to start LiveRooms.index=%d", index) + if index < 0 || index >= len(lr.liveRooms) { + lg.Errorf("[LiveRooms] LiveRooms.index=%d not found", index) + return errors.New("index out of range") + } + lr.liveRooms[index].client.Connect() + return nil +} + +func (lr *LiveRoomController) Disconnect(index int) error { + lg.Infof("[LiveRooms] Try to Disconnect LiveRooms.index=%d", index) + if index < 0 || index >= len(lr.liveRooms) { + lg.Errorf("[LiveRooms] LiveRooms.index=%d not found", index) + return errors.New("index out of range") + } + lr.liveRooms[index].client.Disconnect() + return nil +} + +func (lr *LiveRoomController) AddRoom(clientName, roomId string) (*model.LiveRoom, error) { + rm := &coreLiveRoom{ + LiveRoom: model.LiveRoom{ + ClientName: clientName, + ID: roomId, + AutoConnect: false, + }, + } + lg.Infof("[LiveRooms] add live room %s", &rm.LiveRoom) + err := rm.init(lr.danmuCommandHandler) + if err != nil { + return nil, err + } + lg.Infof("[LiveRooms] %s init failed: %s", &rm.LiveRoom, err) + if err != nil { + return nil, err + } + lr.liveRooms = append(lr.liveRooms, rm) + return &rm.LiveRoom, nil +} + +func (lr *LiveRoomController) DeleteRoom(index int) error { + lg.Infof("Try to remove LiveRooms.index=%d", index) + if index < 0 || index >= len(lr.liveRooms) { + lg.Warnf("LiveRooms.index=%d not found", index) + return errors.New("index out of range") + } + if len(lr.liveRooms) == 1 { + return errors.New("can't delete last room") + } + _ = lr.liveRooms[index].client.Disconnect() + lr.liveRooms[index].EventManager().UnregisterAll() + lr.liveRooms = append(lr.liveRooms[:index], lr.liveRooms[index+1:]...) + return nil +} + +func (lr *LiveRoomController) AddDanmuCommand(executor controller.DanmuCommandExecutor) { + lr.danmuCommands = append(lr.danmuCommands, executor) +} diff --git a/controller/core/lyric.go b/controller/core/lyric.go new file mode 100644 index 0000000..0d4a0b9 --- /dev/null +++ b/controller/core/lyric.go @@ -0,0 +1,56 @@ +package core + +import ( + "AynaLivePlayer/common/event" + "AynaLivePlayer/model" +) + +type LyricLoader struct { + Lyric *model.Lyric + Handler *event.Manager + prev float64 +} + +func NewLyricLoader() *LyricLoader { + return &LyricLoader{ + Lyric: model.LoadLyric(""), + Handler: event.MainManager.NewChildManager(), + prev: -1, + } +} + +func (l *LyricLoader) EventManager() *event.Manager { + return l.Handler +} + +func (l *LyricLoader) Get() *model.Lyric { + return l.Lyric +} + +func (l *LyricLoader) Reload(lyric string) { + l.Lyric = model.LoadLyric(lyric) + l.Handler.CallA( + model.EventLyricReload, + model.LyricReloadEvent{ + Lyrics: l.Lyric, + }) +} + +func (l *LyricLoader) Update(time float64) { + lrc := l.Lyric.Find(time) + if lrc == nil { + return + } + if l.prev == lrc.Time { + return + } + l.prev = lrc.Time + l.Handler.CallA( + model.EventLyricUpdate, + model.LyricUpdateEvent{ + Lyrics: l.Lyric, + Time: time, + Lyric: lrc, + }) + return +} diff --git a/controller/core/playcontrol.go b/controller/core/playcontrol.go new file mode 100644 index 0000000..246fb64 --- /dev/null +++ b/controller/core/playcontrol.go @@ -0,0 +1,242 @@ +package core + +import ( + "AynaLivePlayer/common/event" + "AynaLivePlayer/config" + "AynaLivePlayer/controller" + "AynaLivePlayer/model" + "AynaLivePlayer/player" + "AynaLivePlayer/provider" +) + +type PlayController struct { + eventManager *event.Manager `ini:"-"` + player player.IPlayer `ini:"-"` + playlist controller.IPlaylistController `ini:"-"` + provider controller.IProviderController `ini:"-"` + lyric controller.ILyricLoader `ini:"-"` + playing *model.Media `ini:"-"` + AudioDevice string + Volume float64 + SkipPlaylist bool +} + +func (pc *PlayController) GetSkipPlaylist() bool { + return pc.SkipPlaylist +} + +func (pc *PlayController) SetSkipPlaylist(b bool) { + pc.SkipPlaylist = b +} + +func (pc *PlayController) Name() string { + return "PlayController" +} + +func (pc *PlayController) OnLoad() { + return +} + +func (pc *PlayController) OnSave() { + return +} + +func NewPlayerController( + player player.IPlayer, + playlist controller.IPlaylistController, + lyric controller.ILyricLoader, + provider controller.IProviderController) controller.IPlayController { + pc := &PlayController{ + eventManager: event.MainManager.NewChildManager(), + player: player, + playlist: playlist, + lyric: lyric, + provider: provider, + playing: &model.Media{}, + AudioDevice: "auto", + Volume: 100, + SkipPlaylist: false, + } + config.LoadConfig(pc) + pc.SetVolume(pc.Volume) + pc.SetAudioDevice(pc.AudioDevice) + pc.player.ObserveProperty(model.PlayerPropIdleActive, "controller.playcontrol.idleplaynext", pc.handleMpvIdlePlayNext) + pc.playlist.GetCurrent().EventManager().RegisterA(model.EventPlaylistInsert, "controller.playcontrol.playlistadd", pc.handlePlaylistAdd) + pc.player.ObserveProperty(model.PlayerPropTimePos, "controller.playcontrol.updatelyric", pc.handleLyricUpdate) + return pc +} + +func (pc *PlayController) handleMpvIdlePlayNext(event *event.Event) { + isIdle := event.Data.(model.PlayerPropertyUpdateEvent).Value.(bool) + if isIdle { + lg.Info("[Controller] mpv went idle, try play next") + pc.PlayNext() + } +} + +func (pc *PlayController) handlePlaylistAdd(event *event.Event) { + if pc.player.IsIdle() { + pc.PlayNext() + return + } + lg.Debugf("[PlayController] playlist add event, SkipPlaylist=%t", pc.SkipPlaylist) + if pc.SkipPlaylist && pc.playing != nil && pc.playing.User == controller.PlaylistUser { + pc.PlayNext() + return + } +} + +func (pc *PlayController) handleLyricUpdate(event *event.Event) { + data := event.Data.(model.PlayerPropertyUpdateEvent).Value + if data == nil { + return + } + pc.lyric.Update(data.(float64)) +} + +func (pc *PlayController) EventManager() *event.Manager { + return pc.eventManager +} + +func (pc *PlayController) GetPlaying() *model.Media { + return pc.playing +} + +func (pc *PlayController) GetPlayer() player.IPlayer { + return pc.player +} + +func (pc *PlayController) GetLyric() controller.ILyricLoader { + return pc.lyric +} + +func (pc *PlayController) PlayNext() { + lg.Infof("[PlayController] try to play next possible media") + if pc.playlist.GetCurrent().Size() == 0 && pc.playlist.GetDefault().Size() == 0 { + return + } + var media *model.Media + if pc.playlist.GetCurrent().Size() != 0 { + media = pc.playlist.GetCurrent().Pop().Copy() + } else if pc.playlist.GetDefault().Size() != 0 { + media = pc.playlist.GetDefault().Next().Copy() + media.User = controller.PlaylistUser + } + pc.Play(media) +} + +func (pc *PlayController) Play(media *model.Media) { + lg.Infof("[PlayController] prepare media %s", media.Title) + err := pc.provider.PrepareMedia(media) + if err != nil { + lg.Warn("[PlayController] prepare media failed. try play next") + pc.PlayNext() + return + } + pc.playing = media + pc.playlist.AddToHistory(media) + if err := pc.player.Play(media); err != nil { + lg.Warn("[PlayController] play failed", err) + return + } + pc.eventManager.CallA(model.EventPlay, model.PlayEvent{ + Media: media, + }) + pc.lyric.Reload(media.Lyric) + // reset + media.Url = "" +} + +func (pc *PlayController) Add(keyword string, user interface{}) { + media := pc.provider.MediaMatch(keyword) + if media == nil { + medias, err := pc.provider.Search(keyword) + if err != nil { + lg.Warnf("[PlayController] search for %s, got error %s", keyword, err) + return + } + if len(medias) == 0 { + lg.Info("[PlayController] search for %s, got no result", keyword) + return + } + media = medias[0] + } + media.User = user + lg.Infof("[PlayController] add media %s (%s)", media.Title, media.Artist) + pc.playlist.GetCurrent().Insert(-1, media) +} + +func (pc *PlayController) AddWithProvider(keyword string, pname string, user interface{}) { + media := provider.MatchMedia(pname, keyword) + if media == nil { + medias, err := provider.Search(pname, keyword) + if err != nil { + lg.Warnf("[PlayController] search for %s, got error %s", keyword, err) + return + } + if len(medias) == 0 { + lg.Infof("[PlayController] search for %s, got no result", keyword) + return + } + media = medias[0] + } + media.User = user + lg.Infof("[PlayController] add media %s (%s)", media.Title, media.Artist) + pc.playlist.GetCurrent().Insert(-1, media) +} + +func (pc *PlayController) Seek(position float64, absolute bool) { + if err := pc.player.Seek(position, absolute); err != nil { + lg.Warnf("[PlayController] seek to position %f (%t) failed, %s", position, absolute, err) + } +} + +func (pc *PlayController) Toggle() (b bool) { + var err error + if pc.player.IsPaused() { + err = pc.player.Unpause() + b = false + } else { + err = pc.player.Pause() + b = true + } + if err != nil { + lg.Warn("[PlayController] toggle failed", err) + } + return +} + +func (pc *PlayController) SetVolume(volume float64) { + if pc.player.SetVolume(volume) != nil { + lg.Warnf("[PlayController] set mpv volume to %f failed", volume) + return + } + pc.Volume = volume +} + +func (pc *PlayController) Destroy() { + pc.player.Stop() +} + +func (pc *PlayController) GetCurrentAudioDevice() string { + return pc.AudioDevice +} + +func (pc *PlayController) GetAudioDevices() []model.AudioDevice { + dl, err := pc.player.GetAudioDeviceList() + if err != nil { + return make([]model.AudioDevice, 0) + } + return dl +} + +func (pc *PlayController) SetAudioDevice(device string) { + lg.Infof("[PlayController] set audio device to %s", device) + if err := pc.player.SetAudioDevice(device); err != nil { + lg.Warnf("[PlayController] set mpv audio device to %s failed, %s", device, err) + _ = pc.player.SetAudioDevice("auto") + pc.AudioDevice = "auto" + return + } + pc.AudioDevice = device +} diff --git a/controller/core/playlist.go b/controller/core/playlist.go new file mode 100644 index 0000000..90838e9 --- /dev/null +++ b/controller/core/playlist.go @@ -0,0 +1,397 @@ +package core + +import ( + "AynaLivePlayer/common/event" + "AynaLivePlayer/config" + "AynaLivePlayer/controller" + "AynaLivePlayer/model" + "AynaLivePlayer/provider" + "errors" + "fmt" + "math/rand" + "sync" +) + +type PlaylistController struct { + PlaylistPath string + provider controller.IProviderController + History controller.IPlaylist `ini:"-"` + Current controller.IPlaylist `ini:"-"` + Default controller.IPlaylist `ini:"-"` + Playlists []controller.IPlaylist `ini:"-"` + DefaultIndex int + CurrentPlaylistRandom bool + DefaultPlaylistRandom bool +} + +func NewPlaylistController( + provider controller.IProviderController) controller.IPlaylistController { + pc := &PlaylistController{ + PlaylistPath: "playlist.json", + provider: provider, + History: NewPlaylist("history"), + Default: NewPlaylist("default"), + Current: NewPlaylist("current"), + Playlists: make([]controller.IPlaylist, 0), + DefaultIndex: 0, + CurrentPlaylistRandom: false, + DefaultPlaylistRandom: true, + } + config.LoadConfig(pc) + if pc.DefaultIndex < 0 || pc.DefaultIndex >= len(pc.Playlists) { + pc.DefaultIndex = 0 + lg.Warn("playlist index did not find") + } + go func() { + _ = pc.SetDefault(pc.DefaultIndex) + }() + return pc +} + +func (pc *PlaylistController) Name() string { + return "Playlists" +} + +func (pc *PlaylistController) OnLoad() { + var metas = []model.Meta{ + { + "netease", + "2382819181", + }, + {"netease", + "4987059624", + }, + {"local", + "list1", + }, + } + _ = config.LoadJson(pc.PlaylistPath, &metas) + for _, m := range metas { + p := NewPlaylist(fmt.Sprintf("%s-%s", m.Name, m.Id)) + p.Model().Meta = m + pc.Playlists = append(pc.Playlists, p) + } + if pc.CurrentPlaylistRandom { + pc.Current.Model().Mode = model.PlaylistModeRandom + } + if pc.DefaultPlaylistRandom { + pc.Default.Model().Mode = model.PlaylistModeRandom + } +} + +func (pc *PlaylistController) OnSave() { + var metas = make([]model.Meta, 0) + for _, pl := range pc.Playlists { + metas = append(metas, pl.Model().Meta) + } + _ = config.SaveJson(pc.PlaylistPath, &metas) + + if pc.Current.Model().Mode == model.PlaylistModeRandom { + pc.CurrentPlaylistRandom = true + } else { + pc.CurrentPlaylistRandom = false + } + if pc.Default.Model().Mode == model.PlaylistModeRandom { + pc.DefaultPlaylistRandom = true + } else { + pc.DefaultPlaylistRandom = false + } +} + +func (pc *PlaylistController) Size() int { + return len(pc.Playlists) +} + +func (pc *PlaylistController) GetHistory() controller.IPlaylist { + return pc.History +} + +func (pc *PlaylistController) GetDefault() controller.IPlaylist { + return pc.Default +} + +func (pc *PlaylistController) GetCurrent() controller.IPlaylist { + return pc.Current +} + +func (pc *PlaylistController) AddToHistory(media *model.Media) { + lg.Tracef("add media %s (%s) to history", media.Title, media.Artist) + media = media.Copy() + // reset url for future use + media.Url = "" + if pc.History.Size() >= 1024 { + pc.History.Replace([]*model.Media{}) + } + media.User = controller.HistoryUser + pc.History.Push(media) + return +} + +func (pc *PlaylistController) Get(index int) controller.IPlaylist { + if index < 0 || index >= len(pc.Playlists) { + lg.Warnf("playlist.index=%d not found", index) + return nil + } + return pc.Playlists[index] +} + +func (pc *PlaylistController) Add(pname string, uri string) controller.IPlaylist { + lg.Infof("try add playlist %s with provider %s", uri, pname) + id, err := provider.FormatPlaylistUrl(pname, uri) + if err != nil || id == "" { + lg.Warnf("fail to format %s playlist id for %s", uri, pname) + return nil + } + p := NewPlaylist(fmt.Sprintf("%s-%s", pname, id)) + p.Model().Meta = model.Meta{ + Name: pname, + Id: id, + } + pc.Playlists = append(pc.Playlists, p) + return p +} + +func (pc *PlaylistController) Remove(index int) controller.IPlaylist { + lg.Infof("Try to remove playlist.index=%d", index) + if index < 0 || index >= len(pc.Playlists) { + lg.Warnf("playlist.index=%d not found", index) + return nil + } + if index == pc.DefaultIndex { + lg.Info("Delete current system playlist, reset system playlist to index = 0") + _ = pc.SetDefault(0) + } + if index < pc.DefaultIndex { + lg.Debugf("Delete playlist before system playlist (index=%d), reduce system playlist index by 1", pc.DefaultIndex) + pc.DefaultIndex = pc.DefaultIndex - 1 + } + pl := pc.Playlists[index] + pc.Playlists = append(pc.Playlists[:index], pc.Playlists[index+1:]...) + return pl +} + +func (pc *PlaylistController) SetDefault(index int) error { + lg.Infof("try set system playlist to playlist.id=%d", index) + if index < 0 || index >= len(pc.Playlists) { + lg.Warn("playlist.index=%d not found", index) + return errors.New("playlist.index not found") + } + err := pc.provider.PreparePlaylist(pc.Playlists[index]) + if err != nil { + return err + } + pl := pc.Playlists[index].Model().Copy() + pc.DefaultIndex = index + controller.ApplyUser(pl.Medias, controller.PlaylistUser) + pc.Default.Replace(pl.Medias) + pc.Default.Model().Name = pl.Name + return nil +} + +func (pc *PlaylistController) PreparePlaylistByIndex(index int) error { + lg.Infof("try prepare playlist.id=%d", index) + if index < 0 || index >= len(pc.Playlists) { + lg.Warn("playlist.id=%d not found", index) + return nil + } + return pc.provider.PreparePlaylist(pc.Playlists[index]) +} + +type corePlaylist struct { + model.Playlist + Index int + Lock sync.RWMutex + eventManager *event.Manager +} + +func NewPlaylist(name string) controller.IPlaylist { + return &corePlaylist{ + Index: 0, + Playlist: model.Playlist{ + Name: name, + Medias: make([]*model.Media, 0), + Mode: model.PlaylistModeNormal, + Meta: model.Meta{}, + }, + eventManager: event.MainManager.NewChildManager(), + } +} + +func (p *corePlaylist) Model() *model.Playlist { + return &p.Playlist +} + +func (p *corePlaylist) EventManager() *event.Manager { + return p.eventManager +} + +func (p *corePlaylist) Name() string { + return p.Playlist.Name +} + +func (p *corePlaylist) Size() int { + return p.Playlist.Size() +} + +func (p *corePlaylist) Get(index int) *model.Media { + if index < 0 || index >= p.Playlist.Size() { + return nil + } + return p.Playlist.Medias[index] +} + +func (p *corePlaylist) Pop() *model.Media { + lg.Info("[Playlists] %s pop first media", p.Playlist) + if p.Size() == 0 { + return nil + } + p.Lock.Lock() + index := 0 + if p.Mode == model.PlaylistModeRandom { + index = rand.Intn(p.Size()) + } + m := p.Medias[index] + for i := index; i > 0; i-- { + p.Medias[i] = p.Medias[i-1] + } + p.Medias = p.Medias[1:] + p.Lock.Unlock() + if m == nil { + lg.Warn("[Playlists] pop first media failed, no media left in the playlist") + return nil + } + p.eventManager.CallA( + model.EventPlaylistUpdate, + model.PlaylistUpdateEvent{Playlist: p.Playlist.Copy()}, + ) + return m +} + +func (p *corePlaylist) Replace(medias []*model.Media) { + lg.Infof("[Playlists] %s replace all media", &p.Playlist) + p.Lock.Lock() + p.Playlist.Medias = medias + p.Index = 0 + p.Lock.Unlock() + p.eventManager.CallA( + model.EventPlaylistUpdate, + model.PlaylistUpdateEvent{Playlist: p.Playlist.Copy()}, + ) +} + +func (p *corePlaylist) Push(media *model.Media) { + p.Insert(-1, media) +} + +func (p *corePlaylist) Insert(index int, media *model.Media) { + lg.Infof("[Playlists]insert media into new index %d at %s ", index, p.Playlist) + lg.Debugf("media=%s %v", media.Title, media.Meta) + e := event.Event{ + Id: model.EventPlaylistPreInsert, + Cancelled: false, + Data: model.PlaylistInsertEvent{ + Playlist: p.Playlist.Copy(), + Index: index, + Media: media, + }, + } + p.eventManager.Call(&e) + if e.Cancelled { + lg.Info("[Playlists] media insertion has been cancelled by handler") + return + } + p.Lock.Lock() + if index > p.Size() { + index = p.Size() + } + if index < 0 { + index = p.Size() + index + 1 + } + p.Medias = append(p.Medias, nil) + for i := p.Size() - 1; i > index; i-- { + p.Medias[i] = p.Medias[i-1] + } + p.Medias[index] = media + p.Lock.Unlock() + p.eventManager.CallA( + model.EventPlaylistUpdate, + model.PlaylistUpdateEvent{Playlist: p.Playlist.Copy()}, + ) + p.eventManager.CallA( + model.EventPlaylistInsert, + model.PlaylistInsertEvent{ + Playlist: p.Playlist.Copy(), + Index: index, + Media: media, + }, + ) +} + +func (p *corePlaylist) Delete(index int) *model.Media { + lg.Infof("from media at index %d from %s", index, p.Playlist) + if index >= p.Size() || index < 0 { + p.Lock.Unlock() + return nil + } + m := p.Medias[index] + p.Lock.Lock() + // todo: @5 delete optimization + p.Medias = append(p.Medias[:index], p.Medias[index+1:]...) + p.Lock.Unlock() + if m == nil { + lg.Warnf("media at index %d does not exist", index) + } + p.eventManager.CallA( + model.EventPlaylistUpdate, + model.PlaylistUpdateEvent{Playlist: p.Playlist.Copy()}) + return m +} + +func (p *corePlaylist) Move(src int, dst int) { + lg.Infof("from media from index %d to %d", src, dst) + if src >= p.Size() || src < 0 { + lg.Warnf("media at index %d does not exist", src) + return + } + p.Lock.Lock() + if dst >= p.Size() { + dst = p.Size() - 1 + } + if dst < 0 { + dst = 0 + } + if dst == src { + p.Lock.Unlock() + return + } + step := 1 + if dst < src { + step = -1 + } + tmp := p.Medias[src] + for i := src; i != dst; i += step { + p.Medias[i] = p.Medias[i+step] + } + p.Medias[dst] = tmp + p.Lock.Unlock() + p.eventManager.CallA( + model.EventPlaylistUpdate, + model.PlaylistUpdateEvent{Playlist: p.Playlist.Copy()}) +} + +func (p *corePlaylist) Next() *model.Media { + lg.Infof("[Playlists] %s get next media with random=%t", p, p.Mode == model.PlaylistModeRandom) + if p.Size() == 0 { + lg.Warn("[Playlists] get next media failed, no media left in the playlist") + return nil + } + var index int + index = p.Index + if p.Mode == model.PlaylistModeRandom { + p.Index = rand.Intn(p.Size()) + } else { + p.Index = (p.Index + 1) % p.Size() + } + m := p.Medias[index] + return m +} diff --git a/controller/core/plugin.go b/controller/core/plugin.go new file mode 100644 index 0000000..b2e4136 --- /dev/null +++ b/controller/core/plugin.go @@ -0,0 +1,44 @@ +package core + +import ( + "AynaLivePlayer/controller" + "github.com/sirupsen/logrus" +) + +type PluginController struct { + plugins map[string]controller.Plugin +} + +func NewPluginController() controller.IPluginController { + return &PluginController{ + plugins: make(map[string]controller.Plugin), + } +} + +func (p *PluginController) LoadPlugin(plugin controller.Plugin) { + lg.Info("[Plugin] Loading plugin: " + plugin.Name()) + if _, ok := p.plugins[plugin.Name()]; ok { + logrus.Warnf("[Plugin] plugin with same name already exists, skip") + return + } + if err := plugin.Enable(); err != nil { + lg.Warnf("Failed to load plugin: %s, %s", plugin.Name(), err) + return + } + p.plugins[plugin.Name()] = plugin +} + +func (p *PluginController) LoadPlugins(plugins ...controller.Plugin) { + for _, plugin := range plugins { + p.LoadPlugin(plugin) + } +} + +func (p *PluginController) ClosePlugins() { + for _, plugin := range p.plugins { + if err := plugin.Disable(); err != nil { + lg.Warnf("Failed to close plugin: %s, %s", plugin.Name(), err) + continue + } + } +} diff --git a/controller/core/provider.go b/controller/core/provider.go new file mode 100644 index 0000000..5189cfd --- /dev/null +++ b/controller/core/provider.go @@ -0,0 +1,114 @@ +package core + +import ( + "AynaLivePlayer/config" + "AynaLivePlayer/controller" + "AynaLivePlayer/model" + "AynaLivePlayer/provider" +) + +type ProviderController struct { + config.BaseConfig + Priority []string + LocalDir string +} + +func (pc *ProviderController) Name() string { + return "Provider" +} + +func NewProviderController() controller.IProviderController { + p := &ProviderController{ + Priority: []string{"netease", "kuwo", "bilibili", "local", "bilibili-video"}, + LocalDir: "./music", + } + config.LoadConfig(p) + provider.NewLocal(p.LocalDir) + return p +} + +func (pc *ProviderController) GetPriority() []string { + return pc.Priority +} + +func (pc *ProviderController) PrepareMedia(media *model.Media) error { + var err error + if media.Title == "" || !media.Cover.Exists() { + lg.Trace("fetching media info") + if err = provider.UpdateMedia(media); err != nil { + lg.Warn("fail to prepare media when fetch info", err) + return err + } + } + if media.Url == "" { + lg.Trace("fetching media url") + if err = provider.UpdateMediaUrl(media); err != nil { + lg.Warn("fail to prepare media when url", err) + return err + } + } + if media.Lyric == "" { + lg.Trace("fetching media lyric") + if err = provider.UpdateMediaLyric(media); err != nil { + lg.Warn("fail to prepare media when lyric", err) + } + } + return nil +} + +func (pc *ProviderController) MediaMatch(keyword string) *model.Media { + lg.Infof("Match media for %s", keyword) + for _, p := range pc.Priority { + if pr, ok := provider.Providers[p]; ok { + m := pr.MatchMedia(keyword) + if m == nil { + continue + } + if err := provider.UpdateMedia(m); err == nil { + return m + } + } else { + lg.Warnf("Provider %s not exist", p) + } + } + return nil +} + +func (pc *ProviderController) Search(keyword string) ([]*model.Media, error) { + lg.Infof("Search for %s", keyword) + for _, p := range pc.Priority { + if pr, ok := provider.Providers[p]; ok { + r, err := pr.Search(keyword) + if err != nil { + lg.Warn("Provider %s return err", err) + continue + } + return r, err + } else { + lg.Warnf("Provider %s not exist", p) + } + } + return nil, provider.ErrorNoSuchProvider +} + +func (pc *ProviderController) SearchWithProvider(keyword string, p string) ([]*model.Media, error) { + lg.Infof("Search for %s using %s", keyword, p) + if pr, ok := provider.Providers[p]; ok { + r, err := pr.Search(keyword) + return r, err + } + lg.Warnf("Provider %s not exist", p) + return nil, provider.ErrorNoSuchProvider +} + +func (pc *ProviderController) PreparePlaylist(playlist controller.IPlaylist) error { + lg.Debug("Prepare playlist ", playlist.Name()) + medias, err := provider.GetPlaylist(&playlist.Model().Meta) + if err != nil { + lg.Warn("prepare playlist failed ", err) + return err + } + controller.ApplyUser(medias, controller.SystemUser) + playlist.Replace(medias) + return nil +} diff --git a/controller/danmu.go b/controller/danmu.go deleted file mode 100644 index 24216b3..0000000 --- a/controller/danmu.go +++ /dev/null @@ -1,23 +0,0 @@ -package controller - -import ( - "AynaLivePlayer/event" - "AynaLivePlayer/liveclient" -) - -var DanmuHandlers []DanmuHandler - -type DanmuHandler interface { - Execute(anmu *liveclient.DanmuMessage) -} - -func AddDanmuHandler(handlers ...DanmuHandler) { - DanmuHandlers = append(DanmuHandlers, handlers...) -} - -func danmuHandler(event *event.Event) { - danmu := event.Data.(*liveclient.DanmuMessage) - for _, cmd := range DanmuHandlers { - cmd.Execute(danmu) - } -} diff --git a/controller/global.go b/controller/global.go deleted file mode 100644 index 0a1dc50..0000000 --- a/controller/global.go +++ /dev/null @@ -1,76 +0,0 @@ -package controller - -import ( - "AynaLivePlayer/config" - "AynaLivePlayer/liveclient" - "AynaLivePlayer/player" - "AynaLivePlayer/provider" - "fmt" -) - -var MainPlayer *player.Player -var LiveClient liveclient.LiveClient -var CurrentLyric *player.Lyric -var CurrentMedia *player.Media - -func Initialize() { - MainPlayer = player.NewPlayer() - - SetAudioDevice(config.Player.AudioDevice) - SetVolume(config.Player.Volume) - - UserPlaylist = player.NewPlaylist("user", player.PlaylistConfig{RandomNext: config.Player.UserPlaylistRandom}) - SystemPlaylist = player.NewPlaylist("system", player.PlaylistConfig{RandomNext: config.Player.PlaylistRandom}) - PlaylistManager = make([]*player.Playlist, 0) - History = player.NewPlaylist("history", player.PlaylistConfig{RandomNext: false}) - HistoryUser = &player.User{Name: "History"} - loadPlaylists() - - config.LoadConfig(LiveRoomManager) - LiveRoomManager.InitializeRooms() - - CurrentLyric = player.NewLyric("") - - MainPlayer.ObserveProperty("idle-active", handleMpvIdlePlayNext) - UserPlaylist.Handler.RegisterA(player.EventPlaylistInsert, "controller.playnextwhenadd", handlePlaylistAdd) - MainPlayer.ObserveProperty("time-pos", handleLyricUpdate) - MainPlayer.Start() - -} - -func CloseAndSave() { - // set value to global config - config.Player.PlaylistRandom = SystemPlaylist.Config.RandomNext - config.Player.UserPlaylistRandom = UserPlaylist.Config.RandomNext - _ = config.SaveToConfigFile(config.ConfigPath) -} - -func loadPlaylists() { - l.Info("Loading playlists ", config.Player.Playlists) - if len(config.Player.Playlists) != len(config.Player.Playlists) { - l.Warn("playlist id and provider does not have same length") - return - } - for i := 0; i < len(config.Player.Playlists); i++ { - pc := config.Player.Playlists[i] - p := player.NewPlaylist(fmt.Sprintf("%s-%s", pc.Provider, pc.ID), player.PlaylistConfig{}) - p.Meta = provider.Meta{ - Name: pc.Provider, - Id: pc.ID, - } - PlaylistManager = append(PlaylistManager, p) - } - if config.Player.PlaylistIndex < 0 || config.Player.PlaylistIndex >= len(config.Player.Playlists) { - config.Player.PlaylistIndex = 0 - l.Warn("playlist index did not find") - return - } - go func() { - c := config.Player.PlaylistIndex - err := PreparePlaylist(PlaylistManager[c]) - if err != nil { - return - } - SetSystemPlaylist(c) - }() -} diff --git a/controller/handlers.go b/controller/handlers.go deleted file mode 100644 index 45db92b..0000000 --- a/controller/handlers.go +++ /dev/null @@ -1,35 +0,0 @@ -package controller - -import ( - "AynaLivePlayer/config" - "AynaLivePlayer/event" - "AynaLivePlayer/player" - "github.com/aynakeya/go-mpv" -) - -func handleMpvIdlePlayNext(property *mpv.EventProperty) { - isIdle := property.Data.(mpv.Node).Value.(bool) - if isIdle { - l.Info("mpv went idle, try play next") - PlayNext() - } -} - -func handlePlaylistAdd(event *event.Event) { - if MainPlayer.IsIdle() { - PlayNext() - return - } - if config.Player.SkipPlaylist && CurrentMedia != nil && CurrentMedia.User == player.PlaylistUser { - PlayNext() - return - } -} - -func handleLyricUpdate(property *mpv.EventProperty) { - if property.Data == nil { - return - } - t := property.Data.(mpv.Node).Value.(float64) - CurrentLyric.Update(t) -} diff --git a/controller/liveroom.go b/controller/liveroom.go index 0790747..6a1381a 100644 --- a/controller/liveroom.go +++ b/controller/liveroom.go @@ -1,170 +1,30 @@ package controller import ( - "AynaLivePlayer/config" - "AynaLivePlayer/event" + "AynaLivePlayer/common/event" "AynaLivePlayer/liveclient" - "errors" - "fmt" + "AynaLivePlayer/model" ) -var LiveRoomManager = &LiveRooms{ - LiveRoomPath: "liverooms.json", - LiveRooms: []*LiveRoom{ - { - ClientName: "bilibili", - ID: "9076804", - AutoConnect: false, - }, - { - ClientName: "bilibili", - ID: "3819533", - AutoConnect: false, - }, - }, +type DanmuCommandExecutor interface { + Match(command string) bool + Execute(command string, args []string, danmu *liveclient.DanmuMessage) } -type LiveRooms struct { - LiveRoomPath string - LiveRooms []*LiveRoom `ini:"-"` +type ILiveRoomController interface { + Size() int + Get(index int) ILiveRoom + GetRoomStatus(index int) bool + Connect(index int) error + Disconnect(index int) error + AddRoom(clientName, roomId string) (*model.LiveRoom, error) + DeleteRoom(index int) error + AddDanmuCommand(executor DanmuCommandExecutor) } -func (lr *LiveRooms) Name() string { - return "LiveRoom" -} - -func (lr *LiveRooms) Size() int { - return len(lr.LiveRooms) -} - -func (lr *LiveRooms) OnLoad() { - _ = config.LoadJson(lr.LiveRoomPath, &lr.LiveRooms) -} - -func (lr *LiveRooms) OnSave() { - _ = config.SaveJson(lr.LiveRoomPath, &lr.LiveRooms) -} - -func (lr *LiveRooms) InitializeRooms() { - for i := 0; i < len(lr.LiveRooms); i++ { - if lr.LiveRooms[i].client == nil { - lr.LiveRooms[i].Init() - } - } - go func() { - for i := 0; i < len(lr.LiveRooms); i++ { - if lr.LiveRooms[i].AutoConnect { - go lr.LiveRooms[i].Connect() - } - } - }() -} - -func (lr *LiveRooms) GetRoom(index int) *LiveRoom { - if index < 0 || index >= len(lr.LiveRooms) { - return nil - } - return lr.LiveRooms[index] -} - -func (lr *LiveRooms) AddRoom(clientName, roomId string) (*LiveRoom, error) { - l.Infof("add live client (%s) for %s", clientName, roomId) - rm := &LiveRoom{ - ClientName: clientName, - ID: roomId, - AutoConnect: false, - } - err := rm.Init() - l.Infof("live client (%s) %s init failed", clientName, roomId) - if err != nil { - return nil, err - } - lr.LiveRooms = append(lr.LiveRooms, rm) - return rm, nil -} - -func (lr *LiveRooms) ConnectRoom(index int) error { - l.Infof("Try to start LiveRoom.index=%d", index) - if index < 0 || index >= len(lr.LiveRooms) { - l.Warnf("LiveRoom.index=%d not found", index) - return errors.New("index out of range") - } - lr.LiveRooms[index].client.Connect() - return nil -} - -func (lr *LiveRooms) DisconnectRoom(index int) error { - l.Infof("Try to Disconnect LiveRoom.index=%d", index) - if index < 0 || index >= len(lr.LiveRooms) { - l.Warnf("LiveRoom.index=%d not found", index) - return errors.New("index out of range") - } - lr.LiveRooms[index].client.Disconnect() - return nil -} - -func (lr *LiveRooms) DeleteRoom(index int) error { - l.Infof("Try to remove LiveRoom.index=%d", index) - if index < 0 || index >= len(lr.LiveRooms) { - l.Warnf("LiveRoom.index=%d not found", index) - return errors.New("index out of range") - } - if len(lr.LiveRooms) == 1 { - return errors.New("can't delete last room") - } - lr.LiveRooms[index].client.Handler().UnregisterAll() - _ = lr.LiveRooms[index].Disconnect() - lr.LiveRooms = append(lr.LiveRooms[:index], lr.LiveRooms[index+1:]...) - return nil -} - -type LiveRoom struct { - ClientName string - ID string - AutoConnect bool - client liveclient.LiveClient -} - -func (r *LiveRoom) Init() (err error) { - if r.client != nil { - return nil - } - r.client, err = liveclient.NewLiveClient(r.ClientName, r.ID) - if err != nil { - return - } - r.client.Handler().Register(&event.EventHandler{ - EventId: liveclient.EventMessageReceive, - Name: "controller.commandexecutor", - Handler: danmuCommandHandler, - }) - r.client.Handler().RegisterA( - liveclient.EventMessageReceive, - "controller.danmu.handler", - danmuHandler) - return nil -} - -func (r *LiveRoom) Connect() error { - if r.client == nil { - return errors.New("client hasn't initialized yet") - } - r.client.Connect() - return nil -} - -func (r *LiveRoom) Disconnect() error { - if r.client == nil { - return errors.New("client hasn't initialized yet") - } - r.client.Disconnect() - return nil -} - -func (r *LiveRoom) Title() string { - return fmt.Sprintf("%s-%s", r.ClientName, r.ID) -} - -func (r *LiveRoom) Client() liveclient.LiveClient { - return r.client +type ILiveRoom interface { + Model() *model.LiveRoom // should return mutable model (not a copy) + Title() string // should be same as Model().Title + Status() bool + EventManager() *event.Manager } diff --git a/controller/playcontrol.go b/controller/playcontrol.go new file mode 100644 index 0000000..a08d036 --- /dev/null +++ b/controller/playcontrol.go @@ -0,0 +1,34 @@ +package controller + +import ( + "AynaLivePlayer/common/event" + "AynaLivePlayer/model" + "AynaLivePlayer/player" +) + +type IPlayController interface { + EventManager() *event.Manager + GetPlaying() *model.Media + GetPlayer() player.IPlayer + PlayNext() + Play(media *model.Media) + Add(keyword string, user interface{}) + AddWithProvider(keyword string, provider string, user interface{}) + Seek(position float64, absolute bool) + Toggle() bool + SetVolume(volume float64) + Destroy() + GetCurrentAudioDevice() string + GetAudioDevices() []model.AudioDevice + SetAudioDevice(device string) + GetLyric() ILyricLoader + GetSkipPlaylist() bool + SetSkipPlaylist(b bool) +} + +type ILyricLoader interface { + EventManager() *event.Manager + Get() *model.Lyric + Reload(lyric string) + Update(time float64) +} diff --git a/controller/player.go b/controller/player.go deleted file mode 100644 index 2c5b05c..0000000 --- a/controller/player.go +++ /dev/null @@ -1,130 +0,0 @@ -package controller - -import ( - "AynaLivePlayer/config" - "AynaLivePlayer/player" - "AynaLivePlayer/provider" -) - -func PlayNext() { - l.Info("try to play next possible media") - if UserPlaylist.Size() == 0 && SystemPlaylist.Size() == 0 { - return - } - var media *player.Media - if UserPlaylist.Size() != 0 { - media = UserPlaylist.Pop() - } else if SystemPlaylist.Size() != 0 { - media = SystemPlaylist.Next() - } - Play(media) -} - -func Play(media *player.Media) { - l.Infof("prepare media %s", media.Title) - err := PrepareMedia(media) - if err != nil { - l.Warn("prepare media failed. try play next") - PlayNext() - return - } - CurrentMedia = media - AddToHistory(media) - if err := MainPlayer.Play(media); err != nil { - l.Warn("play failed", err) - return - } - CurrentLyric.Reload(media.Lyric) - // reset - media.Url = "" -} - -func Add(keyword string, user interface{}) { - media := MediaMatch(keyword) - if media == nil { - medias, err := Search(keyword) - if err != nil { - l.Warnf("search for %s, got error %s", keyword, err) - return - } - if len(medias) == 0 { - l.Info("search for %s, got no result", keyword) - return - } - media = medias[0] - } - media.User = user - l.Infof("add media %s (%s)", media.Title, media.Artist) - UserPlaylist.Insert(-1, media) -} - -func AddWithProvider(keyword string, pname string, user interface{}) { - media := provider.MatchMedia(pname, keyword) - if media == nil { - medias, err := provider.Search(pname, keyword) - if err != nil { - l.Warnf("search for %s, got error %s", keyword, err) - return - } - if len(medias) == 0 { - l.Infof("search for %s, got no result", keyword) - return - } - media = medias[0] - } - media.User = user - l.Infof("add media %s (%s)", media.Title, media.Artist) - UserPlaylist.Insert(-1, media) -} - -func Seek(position float64, absolute bool) { - if err := MainPlayer.Seek(position, absolute); err != nil { - l.Warnf("seek to position %f (%t) failed, %s", position, absolute, err) - } -} - -func Toggle() (b bool) { - var err error - if MainPlayer.IsPaused() { - err = MainPlayer.Unpause() - b = false - } else { - err = MainPlayer.Pause() - b = true - } - if err != nil { - l.Warn("toggle failed", err) - } - return -} - -func SetVolume(volume float64) { - if MainPlayer.SetVolume(volume) != nil { - l.Warnf("set mpv volume to %f failed", volume) - return - } - config.Player.Volume = volume -} - -func Destroy() { - MainPlayer.Stop() -} - -func GetAudioDevices() []player.AudioDevice { - dl, err := MainPlayer.GetAudioDeviceList() - if err != nil { - return make([]player.AudioDevice, 0) - } - return dl -} - -func SetAudioDevice(device string) { - l.Infof("set audio device to %s", device) - if err := MainPlayer.SetAudioDevice(device); err != nil { - l.Warnf("set mpv audio device to %s failed, %s", device, err) - MainPlayer.SetAudioDevice("auto") - config.Player.AudioDevice = "auto" - return - } - config.Player.AudioDevice = device -} diff --git a/controller/playlist.go b/controller/playlist.go index a672312..9138fd5 100644 --- a/controller/playlist.go +++ b/controller/playlist.go @@ -1,104 +1,34 @@ package controller import ( - "AynaLivePlayer/config" - "AynaLivePlayer/player" - "AynaLivePlayer/provider" - "fmt" + "AynaLivePlayer/common/event" + "AynaLivePlayer/model" ) -var UserPlaylist *player.Playlist -var History *player.Playlist -var HistoryUser *player.User -var SystemPlaylist *player.Playlist -var PlaylistManager []*player.Playlist - -func AddToHistory(media *player.Media) { - l.Tracef("add media %s (%s) to history", media.Title, media.Artist) - media = media.Copy() - // reset url for future use - media.Url = "" - if History.Size() >= 1024 { - History.Replace([]*player.Media{}) - } - History.Push(media) - return +type IPlaylistController interface { + Size() int + GetHistory() IPlaylist + AddToHistory(media *model.Media) + GetDefault() IPlaylist + GetCurrent() IPlaylist + Get(index int) IPlaylist + Add(pname string, uri string) IPlaylist + Remove(index int) IPlaylist + SetDefault(index int) error + PreparePlaylistByIndex(index int) error } -func ToHistoryMedia(media *player.Media) *player.Media { - media = media.Copy() - media.User = HistoryUser - return media -} - -func ToSystemMedia(media *player.Media) *player.Media { - media = media.Copy() - media.User = player.SystemUser - return media -} - -func AddPlaylist(pname string, uri string) *player.Playlist { - l.Infof("try add playlist %s with provider %s", uri, pname) - id, err := provider.FormatPlaylistUrl(pname, uri) - if err != nil || id == "" { - l.Warnf("fail to format %s playlist id for %s", uri, pname) - return nil - } - p := player.NewPlaylist(fmt.Sprintf("%s-%s", pname, id), player.PlaylistConfig{}) - p.Meta = provider.Meta{ - Name: pname, - Id: id, - } - PlaylistManager = append(PlaylistManager, p) - config.Player.Playlists = append(config.Player.Playlists, &config.PlayerPlaylist{ - ID: uri, - Provider: pname, - }) - return p -} - -func RemovePlaylist(index int) { - l.Infof("Try to remove playlist.index=%d", index) - if index < 0 || index >= len(PlaylistManager) { - l.Warnf("playlist.index=%d not found", index) - return - } - if index == config.Player.PlaylistIndex { - l.Info("Delete current system playlist, reset system playlist to index = 0") - SetSystemPlaylist(0) - } - if index < config.Player.PlaylistIndex { - l.Debugf("Delete playlist before system playlist (index=%d), reduce system playlist index by 1", config.Player.PlaylistIndex) - config.Player.PlaylistIndex = config.Player.PlaylistIndex - 1 - } - PlaylistManager = append(PlaylistManager[:index], PlaylistManager[index+1:]...) - config.Player.Playlists = append(config.Player.Playlists[:index], config.Player.Playlists[index+1:]...) -} - -func SetSystemPlaylist(index int) { - l.Infof("try set system playlist to playlist.id=%d", index) - if index < 0 || index >= len(PlaylistManager) { - l.Warn("playlist.index=%d not found", index) - return - } - err := PreparePlaylist(PlaylistManager[index]) - if err != nil { - return - } - medias := PlaylistManager[index].Playlist - config.Player.PlaylistIndex = index - ApplyUser(medias, player.PlaylistUser) - SystemPlaylist.Replace(medias) -} - -func PreparePlaylistByIndex(index int) { - l.Infof("try prepare playlist.id=%d", index) - if index < 0 || index >= len(PlaylistManager) { - l.Warn("playlist.id=%d not found", index) - return - } - err := PreparePlaylist(PlaylistManager[index]) - if err != nil { - return - } +type IPlaylist interface { + Model() *model.Playlist // mutable model (not a copy) + EventManager() *event.Manager + Name() string + Size() int + Get(index int) *model.Media + Pop() *model.Media + Replace(medias []*model.Media) + Push(media *model.Media) + Insert(index int, media *model.Media) + Delete(index int) *model.Media + Move(src int, dst int) + Next() *model.Media } diff --git a/controller/plugin.go b/controller/plugin.go index ad8e898..5380fe0 100644 --- a/controller/plugin.go +++ b/controller/plugin.go @@ -6,25 +6,8 @@ type Plugin interface { Disable() error } -func LoadPlugin(plugin Plugin) { - l.Info("Loading plugin: " + plugin.Name()) - if err := plugin.Enable(); err != nil { - l.Warnf("Failed to load plugin: %s, %s", plugin.Name(), err) - } -} - -func LoadPlugins(plugins ...Plugin) { - for _, plugin := range plugins { - LoadPlugin(plugin) - } -} - -func ClosePlugins(plugins ...Plugin) { - for _, plugin := range plugins { - err := plugin.Disable() - if err != nil { - l.Warnf("Failed to close plugin: %s, %s", plugin.Name(), err) - return - } - } +type IPluginController interface { + LoadPlugin(plugin Plugin) + LoadPlugins(plugins ...Plugin) + ClosePlugins() } diff --git a/controller/provider.go b/controller/provider.go index c848cd9..d2c2249 100644 --- a/controller/provider.go +++ b/controller/provider.go @@ -1,95 +1,30 @@ package controller import ( - "AynaLivePlayer/config" - "AynaLivePlayer/player" - "AynaLivePlayer/provider" + "AynaLivePlayer/model" ) -func PrepareMedia(media *player.Media) error { - var err error - if media.Title == "" || !media.Cover.Exists() { - l.Trace("fetching media info") - if err = provider.UpdateMedia(media); err != nil { - l.Warn("fail to prepare media when fetch info", err) - return err - } - } - if media.Url == "" { - l.Trace("fetching media url") - if err = provider.UpdateMediaUrl(media); err != nil { - l.Warn("fail to prepare media when url", err) - return err - } - } - if media.Lyric == "" { - l.Trace("fetching media lyric") - if err = provider.UpdateMediaLyric(media); err != nil { - l.Warn("fail to prepare media when lyric", err) - } - } - return nil +var PlaylistUser = &model.User{Name: "Playlists"} +var SystemUser = &model.User{Name: "System"} +var HistoryUser = &model.User{Name: "History"} + +type IProviderController interface { + GetPriority() []string + PrepareMedia(media *model.Media) error + MediaMatch(keyword string) *model.Media + Search(keyword string) ([]*model.Media, error) + SearchWithProvider(keyword string, provider string) ([]*model.Media, error) + PreparePlaylist(playlist IPlaylist) error } -func MediaMatch(keyword string) *player.Media { - l.Infof("Match media for %s", keyword) - for _, p := range config.Provider.Priority { - if pr, ok := provider.Providers[p]; ok { - m := pr.MatchMedia(keyword) - if m == nil { - continue - } - if err := provider.UpdateMedia(m); err == nil { - return m - } - } else { - l.Warnf("Provider %s not exist", p) - } - } - return nil -} - -func Search(keyword string) ([]*player.Media, error) { - l.Infof("Search for %s", keyword) - for _, p := range config.Provider.Priority { - if pr, ok := provider.Providers[p]; ok { - r, err := pr.Search(keyword) - if err != nil { - l.Warn("Provider %s return err", err) - continue - } - return r, err - } else { - l.Warnf("Provider %s not exist", p) - } - } - return nil, provider.ErrorNoSuchProvider -} - -func SearchWithProvider(keyword string, p string) ([]*player.Media, error) { - l.Infof("Search for %s using %s", keyword, p) - if pr, ok := provider.Providers[p]; ok { - r, err := pr.Search(keyword) - return r, err - } - l.Warnf("Provider %s not exist", p) - return nil, provider.ErrorNoSuchProvider -} - -func ApplyUser(medias []*player.Media, user interface{}) { +func ApplyUser(medias []*model.Media, user interface{}) { for _, m := range medias { m.User = user } } -func PreparePlaylist(playlist *player.Playlist) error { - l.Debug("Prepare playlist ", playlist.Meta.(provider.Meta)) - medias, err := provider.GetPlaylist(playlist.Meta.(provider.Meta)) - if err != nil { - l.Warn("prepare playlist failed ", err) - return err - } - ApplyUser(medias, player.SystemUser) - playlist.Replace(medias) - return nil +func ToSpMedia(media *model.Media, user *model.User) *model.Media { + media = media.Copy() + media.User = user + return media } diff --git a/event/event.go b/event/event.go deleted file mode 100644 index ff12cc9..0000000 --- a/event/event.go +++ /dev/null @@ -1,88 +0,0 @@ -package event - -import ( - "AynaLivePlayer/logger" - "github.com/sirupsen/logrus" - "sync" -) - -type EventId string - -const MODULE_HANDLER = "EventHandler" - -var eventLogger = logger.Logger.WithFields(logrus.Fields{ - "Module": MODULE_HANDLER, -}) - -type Event struct { - Id EventId - Cancelled bool - Data interface{} -} - -type EventHandlerFunc func(event *Event) - -type EventHandler struct { - EventId EventId - Name string - Handler EventHandlerFunc -} - -type Handler struct { - handlers map[string]*EventHandler - lock sync.RWMutex -} - -func NewHandler() *Handler { - return &Handler{ - handlers: make(map[string]*EventHandler), - } -} - -func (h *Handler) Register(handler *EventHandler) { - h.lock.Lock() - defer h.lock.Unlock() - eventLogger.Tracef("register new handler id=%s,name=%s", handler.EventId, handler.Name) - h.handlers[handler.Name] = handler -} - -func (h *Handler) RegisterA(id EventId, name string, handler EventHandlerFunc) { - h.Register(&EventHandler{ - EventId: id, - Name: name, - Handler: handler, - }) -} - -func (h *Handler) UnregisterAll() { - h.lock.Lock() - defer h.lock.Unlock() - eventLogger.Trace("clear all handler") - h.handlers = make(map[string]*EventHandler) -} - -func (h *Handler) Unregister(name string) { - h.lock.Lock() - defer h.lock.Unlock() - eventLogger.Tracef("unregister handler name=%s", name) - delete(h.handlers, name) -} - -func (h *Handler) Call(event *Event) { - h.lock.RLock() - defer h.lock.RUnlock() - for _, eh := range h.handlers { - if eh.EventId == event.Id { - eventLogger.Tracef("handler name=%s called by event_id = %s", event.Id, eh.Name) - // todo: @3 - go eh.Handler(event) - } - } -} - -func (h *Handler) CallA(id EventId, data interface{}) { - h.Call(&Event{ - Id: id, - Data: data, - }) -} diff --git a/go.mod b/go.mod index e4b6483..6b62a52 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module AynaLivePlayer go 1.19 require ( - fyne.io/fyne/v2 v2.2.3 + fyne.io/fyne/v2 v2.2.4 github.com/XiaoMengXinX/Music163Api-Go v0.1.26 github.com/antonfisher/nested-logrus-formatter v1.3.1 github.com/aynakeya/blivedm v0.1.6 diff --git a/go.sum b/go.sum index 8bbec11..da53244 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= fyne.io/fyne/v2 v2.2.3 h1:Umi3vVVW8XnWWPJmMkhIWQOMU/jxB1OqpWVUmjhODD0= fyne.io/fyne/v2 v2.2.3/go.mod h1:MBoGuHzLLSXdQOWFAwWhIhYTEMp33zqtGCReSWhaQTA= +fyne.io/fyne/v2 v2.2.4 h1:izyiDUjJYAB7B/MST7M9GDs+mQ0CwDgRZTiVJZQoEe4= +fyne.io/fyne/v2 v2.2.4/go.mod h1:MBoGuHzLLSXdQOWFAwWhIhYTEMp33zqtGCReSWhaQTA= fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93 h1:V2IC9t0Zj9Ur6qDbfhUuzVmIvXKFyxZXRJyigUvovs4= fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= diff --git a/gui/config_basic.go b/gui/config_basic.go index 05ec7dd..87dd2c7 100644 --- a/gui/config_basic.go +++ b/gui/config_basic.go @@ -1,12 +1,11 @@ package gui import ( - "AynaLivePlayer/config" + "AynaLivePlayer/common/i18n" "AynaLivePlayer/controller" - "AynaLivePlayer/i18n" + "AynaLivePlayer/model" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/widget" ) @@ -26,16 +25,33 @@ func (b *bascicConfig) CreatePanel() fyne.CanvasObject { if b.panel != nil { return b.panel } + randomPlaylist := container.NewHBox( widget.NewLabel(i18n.T("gui.config.basic.random_playlist")), - widget.NewCheckWithData( + newCheckInit( i18n.T("gui.config.basic.random_playlist.user"), - binding.BindBool(&controller.UserPlaylist.Config.RandomNext)), - widget.NewCheckWithData( + func(b bool) { + l().Infof("Set random playlist for user: %t", b) + if b { + controller.Instance.Playlists().GetCurrent().Model().Mode = model.PlaylistModeRandom + } else { + controller.Instance.Playlists().GetCurrent().Model().Mode = model.PlaylistModeNormal + } + }, + controller.Instance.Playlists().GetCurrent().Model().Mode == model.PlaylistModeRandom), + newCheckInit( i18n.T("gui.config.basic.random_playlist.system"), - binding.BindBool(&controller.SystemPlaylist.Config.RandomNext)), + func(b bool) { + l().Infof("Set random playlist for system: %t", b) + if b { + controller.Instance.Playlists().GetDefault().Model().Mode = model.PlaylistModeRandom + } else { + controller.Instance.Playlists().GetDefault().Model().Mode = model.PlaylistModeNormal + } + }, + controller.Instance.Playlists().GetDefault().Model().Mode == model.PlaylistModeRandom), ) - devices := controller.GetAudioDevices() + devices := controller.Instance.PlayControl().GetAudioDevices() deviceDesc := make([]string, len(devices)) deviceDesc2Name := make(map[string]string) for i, device := range devices { @@ -43,17 +59,20 @@ func (b *bascicConfig) CreatePanel() fyne.CanvasObject { deviceDesc2Name[device.Description] = device.Name } deviceSel := widget.NewSelect(deviceDesc, func(s string) { - controller.SetAudioDevice(deviceDesc2Name[s]) + controller.Instance.PlayControl().SetAudioDevice(deviceDesc2Name[s]) }) - deviceSel.Selected = config.Player.AudioDevice + deviceSel.Selected = controller.Instance.PlayControl().GetCurrentAudioDevice() outputDevice := container.NewBorder(nil, nil, widget.NewLabel(i18n.T("gui.config.basic.audio_device")), nil, deviceSel) skipPlaylist := container.NewHBox( widget.NewLabel(i18n.T("gui.config.basic.skip_playlist")), - widget.NewCheckWithData( + newCheckInit( i18n.T("gui.config.basic.skip_playlist.prompt"), - binding.BindBool(&config.Player.SkipPlaylist), + func(b bool) { + controller.Instance.PlayControl().SetSkipPlaylist(b) + }, + controller.Instance.PlayControl().GetSkipPlaylist(), ), ) b.panel = container.NewVBox(randomPlaylist, outputDevice, skipPlaylist) diff --git a/gui/gui.go b/gui/gui.go index cb7a1cd..069343c 100644 --- a/gui/gui.go +++ b/gui/gui.go @@ -1,9 +1,9 @@ package gui import ( + "AynaLivePlayer/common/i18n" + "AynaLivePlayer/common/logger" "AynaLivePlayer/config" - "AynaLivePlayer/i18n" - "AynaLivePlayer/logger" "AynaLivePlayer/resource" "fmt" "fyne.io/fyne/v2" diff --git a/gui/helper.go b/gui/helper.go index 70d05a6..aa950f2 100644 --- a/gui/helper.go +++ b/gui/helper.go @@ -1,12 +1,13 @@ package gui import ( - "AynaLivePlayer/player" + "AynaLivePlayer/model" "bytes" "errors" "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/storage" "fyne.io/fyne/v2/widget" ) @@ -80,7 +81,7 @@ func newFixedSplitContainer(horizontal bool, leading, trailing fyne.CanvasObject return fs } -func newImageFromPlayerPicture(picture player.Picture) (*canvas.Image, error) { +func newImageFromPlayerPicture(picture model.Picture) (*canvas.Image, error) { if picture.Data != nil { img := canvas.NewImageFromReader(bytes.NewReader(picture.Data), "cover") // return an error when img is nil @@ -104,3 +105,15 @@ func newImageFromPlayerPicture(picture player.Picture) (*canvas.Image, error) { return img, nil } } + +func showDialogIfError(err error) { + if err != nil { + dialog.ShowError(err, MainWindow) + } +} + +func newCheckInit(name string, changed func(bool), checked bool) *widget.Check { + check := widget.NewCheck(name, changed) + check.SetChecked(checked) + return check +} diff --git a/gui/history_list.go b/gui/history_list.go index adfb1ab..4414de9 100644 --- a/gui/history_list.go +++ b/gui/history_list.go @@ -1,24 +1,26 @@ package gui import ( + "AynaLivePlayer/common/event" + "AynaLivePlayer/common/i18n" "AynaLivePlayer/controller" - "AynaLivePlayer/event" - "AynaLivePlayer/i18n" - "AynaLivePlayer/player" + "AynaLivePlayer/model" "fmt" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" + "sync" ) var History = &struct { - Playlist *player.Playlist + Playlist *model.Playlist List *widget.List + mux sync.RWMutex }{} func createHistoryList() fyne.CanvasObject { - History.Playlist = controller.History + History.Playlist = controller.Instance.Playlists().GetHistory().Model().Copy() History.List = widget.NewList( func() int { return History.Playlist.Size() @@ -36,7 +38,7 @@ func createHistoryList() fyne.CanvasObject { newLabelWithWrapping("user", fyne.TextTruncate))) }, func(id widget.ListItemID, object fyne.CanvasObject) { - m := History.Playlist.Playlist[History.Playlist.Size()-id-1] + m := History.Playlist.Medias[History.Playlist.Size()-id-1].Copy() object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*widget.Label).SetText( m.Title) object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText( @@ -45,11 +47,12 @@ func createHistoryList() fyne.CanvasObject { m.ToUser().Name) object.(*fyne.Container).Objects[1].(*widget.Label).SetText(fmt.Sprintf("%d", id)) btns := object.(*fyne.Container).Objects[2].(*fyne.Container).Objects + m.User = controller.HistoryUser btns[0].(*widget.Button).OnTapped = func() { - controller.Play(controller.ToHistoryMedia(m)) + controller.Instance.PlayControl().Play(m) } btns[1].(*widget.Button).OnTapped = func() { - controller.UserPlaylist.Push(controller.ToHistoryMedia(m)) + controller.Instance.Playlists().GetCurrent().Push(m) } }) registerHistoryHandler() @@ -66,9 +69,10 @@ func createHistoryList() fyne.CanvasObject { } func registerHistoryHandler() { - History.Playlist.Handler.RegisterA(player.EventPlaylistUpdate, "gui.history.update", func(event *event.Event) { - History.Playlist.Lock.RLock() + controller.Instance.Playlists().GetHistory().EventManager().RegisterA(model.EventPlaylistUpdate, "gui.history.update", func(event *event.Event) { + History.mux.RLock() + History.Playlist = event.Data.(model.PlaylistUpdateEvent).Playlist History.List.Refresh() - History.Playlist.Lock.RUnlock() + History.mux.RUnlock() }) } diff --git a/gui/player_controller.go b/gui/player_controller.go index 72e4dbd..5cd3af1 100644 --- a/gui/player_controller.go +++ b/gui/player_controller.go @@ -1,18 +1,17 @@ package gui import ( + "AynaLivePlayer/common/event" + "AynaLivePlayer/common/i18n" + "AynaLivePlayer/common/util" "AynaLivePlayer/config" "AynaLivePlayer/controller" - "AynaLivePlayer/event" - "AynaLivePlayer/i18n" - "AynaLivePlayer/player" - "AynaLivePlayer/util" + "AynaLivePlayer/model" "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" - "github.com/aynakeya/go-mpv" ) type PlayControllerContainer struct { @@ -76,13 +75,13 @@ func createPlayController() fyne.CanvasObject { func registerPlayControllerHandler() { PlayController.ButtonPrev.OnTapped = func() { - controller.Seek(0, true) + controller.Instance.PlayControl().Seek(0, true) } PlayController.ButtonSwitch.OnTapped = func() { - controller.Toggle() + controller.Instance.PlayControl().Toggle() } PlayController.ButtonNext.OnTapped = func() { - controller.PlayNext() + controller.Instance.PlayControl().PlayNext() } PlayController.ButtonLrc.OnTapped = func() { @@ -92,94 +91,104 @@ func registerPlayControllerHandler() { } } - if controller.MainPlayer.ObserveProperty("pause", func(property *mpv.EventProperty) { - if property.Data == nil { - PlayController.ButtonSwitch.Icon = theme.MediaPlayIcon() - return - } - if property.Data.(mpv.Node).Value.(bool) { - PlayController.ButtonSwitch.Icon = theme.MediaPlayIcon() - } else { - PlayController.ButtonSwitch.Icon = theme.MediaPauseIcon() - } - }) != nil { + if controller.Instance.PlayControl().GetPlayer().ObserveProperty( + model.PlayerPropPause, "gui.play_controller.pause", func(ev *event.Event) { + data := ev.Data.(model.PlayerPropertyUpdateEvent).Value + if data == nil { + PlayController.ButtonSwitch.Icon = theme.MediaPlayIcon() + return + } + if data.(bool) { + PlayController.ButtonSwitch.Icon = theme.MediaPlayIcon() + } else { + PlayController.ButtonSwitch.Icon = theme.MediaPauseIcon() + } + }) != nil { l().Error("fail to register handler for switch button with property pause") } - if controller.MainPlayer.ObserveProperty("percent-pos", func(property *mpv.EventProperty) { - if property.Data == nil { - PlayController.Progress.Value = 0 - } else { - PlayController.Progress.Value = property.Data.(mpv.Node).Value.(float64) * 10 - } - PlayController.Progress.Refresh() - }) != nil { + if controller.Instance.PlayControl().GetPlayer().ObserveProperty( + model.PlayerPropPercentPos, "gui.play_controller.percent_pos", func(ev *event.Event) { + data := ev.Data.(model.PlayerPropertyUpdateEvent).Value + if data == nil { + PlayController.Progress.Value = 0 + } else { + PlayController.Progress.Value = data.(float64) * 10 + } + PlayController.Progress.Refresh() + }) != nil { l().Error("fail to register handler for progress bar with property percent-pos") } - if controller.MainPlayer.ObserveProperty("idle-active", func(property *mpv.EventProperty) { - isIdle := property.Data.(mpv.Node).Value.(bool) - l().Debug("receive idle active ", isIdle, " set/reset info") - // todo: @3 - if isIdle { - PlayController.Progress.Value = 0 - PlayController.Progress.Max = 0 - //PlayController.Title.SetText("Title") - //PlayController.Artist.SetText("Artist") - //PlayController.Username.SetText("Username") - //PlayController.SetDefaultCover() - } else { - PlayController.Progress.Max = 1000 - } - }) != nil { + if controller.Instance.PlayControl().GetPlayer().ObserveProperty( + model.PlayerPropIdleActive, "gui.play_controller.idle_active", func(ev *event.Event) { + isIdle := ev.Data.(model.PlayerPropertyUpdateEvent).Value.(bool) + l().Debug("receive idle active ", isIdle, " set/reset info") + // todo: @3 + if isIdle { + PlayController.Progress.Value = 0 + PlayController.Progress.Max = 0 + //PlayController.Title.SetText("Title") + //PlayController.Artist.SetText("Artist") + //PlayController.Username.SetText("Username") + //PlayController.SetDefaultCover() + } else { + PlayController.Progress.Max = 1000 + } + }) != nil { l().Error("fail to register handler for progress bar with property idle-active") } PlayController.Progress.Max = 0 PlayController.Progress.OnChanged = func(f float64) { - controller.Seek(f/10, false) + controller.Instance.PlayControl().Seek(f/10, false) } - if controller.MainPlayer.ObserveProperty("time-pos", func(property *mpv.EventProperty) { - if property.Data == nil { - PlayController.CurrentTime.SetText("0:00") - return - } - PlayController.CurrentTime.SetText(util.FormatTime(int(property.Data.(mpv.Node).Value.(float64)))) - }) != nil { + if controller.Instance.PlayControl().GetPlayer().ObserveProperty( + model.PlayerPropTimePos, "gui.play_controller.time_pos", func(ev *event.Event) { + data := ev.Data.(model.PlayerPropertyUpdateEvent).Value + if data == nil { + PlayController.CurrentTime.SetText("0:00") + return + } + PlayController.CurrentTime.SetText(util.FormatTime(int(data.(float64)))) + }) != nil { l().Error("fail to register handler for current time with property time-pos") } - if controller.MainPlayer.ObserveProperty("duration", func(property *mpv.EventProperty) { - if property.Data == nil { - PlayController.TotalTime.SetText("0:00") - return - } - PlayController.TotalTime.SetText(util.FormatTime(int(property.Data.(mpv.Node).Value.(float64)))) - }) != nil { + if controller.Instance.PlayControl().GetPlayer().ObserveProperty( + model.PlayerPropDuration, "gui.play_controller.duration", func(ev *event.Event) { + data := ev.Data.(model.PlayerPropertyUpdateEvent).Value + if data == nil { + PlayController.TotalTime.SetText("0:00") + return + } + PlayController.TotalTime.SetText(util.FormatTime(int(data.(float64)))) + }) != nil { l().Error("fail to register handler for total time with property duration") } - if controller.MainPlayer.ObserveProperty("volume", func(property *mpv.EventProperty) { - l().Trace("receive volume change event", *property) - if property.Data == nil { - PlayController.Volume.Value = 0 - } else { - PlayController.Volume.Value = property.Data.(mpv.Node).Value.(float64) - } + if controller.Instance.PlayControl().GetPlayer().ObserveProperty( + model.PlayerPropVolume, "gui.play_controller.volume", func(ev *event.Event) { + data := ev.Data.(model.PlayerPropertyUpdateEvent).Value + if data == nil { + PlayController.Volume.Value = 0 + } else { + PlayController.Volume.Value = data.(float64) + } - PlayController.Volume.Refresh() - }) != nil { + PlayController.Volume.Refresh() + }) != nil { l().Error("fail to register handler for progress bar with property percent-pos") } PlayController.Volume.OnChanged = func(f float64) { - controller.SetVolume(f) + controller.Instance.PlayControl().SetVolume(f) } - controller.MainPlayer.EventHandler.RegisterA(player.EventPlay, "gui.player.updateinfo", func(event *event.Event) { + controller.Instance.PlayControl().EventManager().RegisterA(model.EventPlay, "gui.player.updateinfo", func(event *event.Event) { l().Debug("receive EventPlay update player info") - media := event.Data.(player.PlayEvent).Media + media := event.Data.(model.PlayEvent).Media //PlayController.Title.SetText( // util.StringNormalize(media.Title, 16, 16)) //PlayController.Artist.SetText( diff --git a/gui/player_lyric.go b/gui/player_lyric.go index f4b1cd2..48aa53a 100644 --- a/gui/player_lyric.go +++ b/gui/player_lyric.go @@ -1,9 +1,9 @@ package gui import ( + "AynaLivePlayer/common/event" "AynaLivePlayer/controller" - "AynaLivePlayer/event" - "AynaLivePlayer/player" + "AynaLivePlayer/model" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/widget" @@ -16,9 +16,9 @@ func createLyricWindow() fyne.Window { w := App.NewWindow("Lyric") currentLrc := newLabelWithWrapping("", fyne.TextWrapBreak) currentLrc.Alignment = fyne.TextAlignCenter - lrcs := make([]string, len(controller.CurrentLyric.Lyrics)) + lrcs := make([]string, len(controller.Instance.PlayControl().GetLyric().Get().Lyrics)) for i := 0; i < len(lrcs); i++ { - lrcs[i] = controller.CurrentLyric.Lyrics[i].Lyric + lrcs[i] = controller.Instance.PlayControl().GetLyric().Get().Lyrics[i].Lyric } fullLrc := widget.NewRichTextWithText(strings.Join(lrcs, "\n\n")) fullLrc.Scroll = container.ScrollVerticalOnly @@ -31,30 +31,32 @@ func createLyricWindow() fyne.Window { w.CenterOnScreen() // register handlers - controller.CurrentLyric.Handler.RegisterA(player.EventLyricUpdate, "player.lyric.current_lyric", func(event *event.Event) { - e := event.Data.(player.LyricUpdateEvent) - if e.Lyric == nil { - currentLrc.SetText("") - return - } - currentLrc.SetText(e.Lyric.Lyric) - }) - controller.CurrentLyric.Handler.RegisterA(player.EventLyricReload, "player.lyric.new_media", func(event *event.Event) { - e := event.Data.(player.LyricReloadEvent) - lrcs := make([]string, len(e.Lyrics.Lyrics)) - for i := 0; i < len(lrcs); i++ { - lrcs[i] = e.Lyrics.Lyrics[i].Lyric - } - fullLrc.Segments[0] = &widget.TextSegment{ - Style: widget.RichTextStyleInline, - Text: strings.Join(lrcs, "\n\n"), - } - fullLrc.Refresh() - }) + controller.Instance.PlayControl().GetLyric().EventManager().RegisterA( + model.EventLyricUpdate, "player.lyric.current_lyric", func(event *event.Event) { + e := event.Data.(model.LyricUpdateEvent) + if e.Lyric == nil { + currentLrc.SetText("") + return + } + currentLrc.SetText(e.Lyric.Lyric) + }) + controller.Instance.PlayControl().GetLyric().EventManager().RegisterA( + model.EventLyricReload, "player.lyric.new_media", func(event *event.Event) { + e := event.Data.(model.LyricReloadEvent) + lrcs := make([]string, len(e.Lyrics.Lyrics)) + for i := 0; i < len(lrcs); i++ { + lrcs[i] = e.Lyrics.Lyrics[i].Lyric + } + fullLrc.Segments[0] = &widget.TextSegment{ + Style: widget.RichTextStyleInline, + Text: strings.Join(lrcs, "\n\n"), + } + fullLrc.Refresh() + }) w.SetOnClosed(func() { - controller.CurrentLyric.Handler.Unregister("player.lyric.current_lyric") - controller.CurrentLyric.Handler.Unregister("player.lyric.new_media") + controller.Instance.PlayControl().GetLyric().EventManager().Unregister("player.lyric.current_lyric") + controller.Instance.PlayControl().GetLyric().EventManager().Unregister("player.lyric.new_media") PlayController.LrcWindowOpen = false }) return w diff --git a/gui/player_playlist.go b/gui/player_playlist.go index 9cdf4b0..b7a87ed 100644 --- a/gui/player_playlist.go +++ b/gui/player_playlist.go @@ -1,15 +1,16 @@ package gui import ( + "AynaLivePlayer/common/event" + "AynaLivePlayer/common/i18n" "AynaLivePlayer/controller" - "AynaLivePlayer/event" - "AynaLivePlayer/i18n" - "AynaLivePlayer/player" + "AynaLivePlayer/model" "fmt" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" + "sync" ) type playlistOperationButton struct { @@ -25,10 +26,10 @@ func (b *playlistOperationButton) Tapped(e *fyne.PointEvent) { func newPlaylistOperationButton() *playlistOperationButton { b := &playlistOperationButton{Index: 0} deleteItem := fyne.NewMenuItem(i18n.T("gui.player.playlist.op.delete"), func() { - controller.UserPlaylist.Delete(b.Index) + controller.Instance.Playlists().GetCurrent().Delete(b.Index) }) topItem := fyne.NewMenuItem(i18n.T("gui.player.playlist.op.top"), func() { - controller.UserPlaylist.Move(b.Index, 0) + controller.Instance.Playlists().GetCurrent().Move(b.Index, 0) }) m := fyne.NewMenu("", deleteItem, topItem) b.menu = m @@ -38,15 +39,14 @@ func newPlaylistOperationButton() *playlistOperationButton { return b } -type PlaylistContainer struct { - Playlist *player.Playlist +var UserPlaylist = &struct { + Playlist *model.Playlist List *widget.List -} - -var UserPlaylist = &PlaylistContainer{} + mux sync.RWMutex +}{} func createPlaylist() fyne.CanvasObject { - UserPlaylist.Playlist = controller.UserPlaylist + UserPlaylist.Playlist = controller.Instance.Playlists().GetCurrent().Model().Copy() UserPlaylist.List = widget.NewList( func() int { //debug.PrintStack() @@ -62,11 +62,11 @@ func createPlaylist() fyne.CanvasObject { }, func(id widget.ListItemID, object fyne.CanvasObject) { object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*widget.Label).SetText( - UserPlaylist.Playlist.Playlist[id].Title) + UserPlaylist.Playlist.Medias[id].Title) object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText( - UserPlaylist.Playlist.Playlist[id].Artist) + UserPlaylist.Playlist.Medias[id].Artist) object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[2].(*widget.Label).SetText( - UserPlaylist.Playlist.Playlist[id].ToUser().Name) + UserPlaylist.Playlist.Medias[id].ToUser().Name) object.(*fyne.Container).Objects[1].(*widget.Label).SetText(fmt.Sprintf("%d", id)) object.(*fyne.Container).Objects[2].(*playlistOperationButton).Index = id }) @@ -85,10 +85,12 @@ func createPlaylist() fyne.CanvasObject { } func registerPlaylistHandler() { - UserPlaylist.Playlist.Handler.RegisterA(player.EventPlaylistUpdate, "gui.playlist.update", func(event *event.Event) { - // @6 Read lock Playlist when updating free after updating. - UserPlaylist.Playlist.Lock.RLock() + controller.Instance.Playlists().GetCurrent().EventManager().RegisterA(model.EventPlaylistUpdate, "gui.playlist.update", func(event *event.Event) { + // Read lock Playlists when updating free after updating. + l().Tracef("Playlist update event received: %s", event.Data.(model.PlaylistUpdateEvent).Playlist) + UserPlaylist.mux.RLock() + UserPlaylist.Playlist = event.Data.(model.PlaylistUpdateEvent).Playlist UserPlaylist.List.Refresh() - UserPlaylist.Playlist.Lock.RUnlock() + UserPlaylist.mux.RUnlock() }) } diff --git a/gui/playlist_list.go b/gui/playlist_list.go index e6cb737..fdd9e36 100644 --- a/gui/playlist_list.go +++ b/gui/playlist_list.go @@ -1,9 +1,8 @@ package gui import ( - "AynaLivePlayer/config" + "AynaLivePlayer/common/i18n" "AynaLivePlayer/controller" - "AynaLivePlayer/i18n" "fmt" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" @@ -25,10 +24,7 @@ type PlaylistManagerContainer struct { } func (p *PlaylistManagerContainer) UpdateCurrentSystemPlaylist() { - if config.Player.PlaylistIndex >= len(controller.PlaylistManager) { - p.CurrentSystemPlaylist.SetText(i18n.T("gui.playlist.current.none")) - } - p.CurrentSystemPlaylist.SetText(i18n.T("gui.playlist.current") + controller.PlaylistManager[config.Player.PlaylistIndex].Name) + p.CurrentSystemPlaylist.SetText(i18n.T("gui.playlist.current") + controller.Instance.Playlists().GetDefault().Name()) } var PlaylistManager = &PlaylistManagerContainer{} @@ -36,17 +32,17 @@ var PlaylistManager = &PlaylistManagerContainer{} func createPlaylists() fyne.CanvasObject { PlaylistManager.Playlists = widget.NewList( func() int { - return len(controller.PlaylistManager) + return controller.Instance.Playlists().Size() }, func() fyne.CanvasObject { return widget.NewLabel("AAAAAAAAAAAAAAAA") }, func(id widget.ListItemID, object fyne.CanvasObject) { object.(*widget.Label).SetText( - controller.PlaylistManager[id].Name) + controller.Instance.Playlists().Get(id).Name()) }) PlaylistManager.AddBtn = widget.NewButton(i18n.T("gui.playlist.button.add"), func() { - providerEntry := widget.NewSelect(config.Provider.Priority, nil) + providerEntry := widget.NewSelect(controller.Instance.Provider().GetPriority(), nil) idEntry := widget.NewEntry() dia := dialog.NewCustomConfirm( i18n.T("gui.playlist.add.title"), @@ -64,7 +60,7 @@ func createPlaylists() fyne.CanvasObject { ), func(b bool) { if b && len(providerEntry.Selected) > 0 && len(idEntry.Text) > 0 { - controller.AddPlaylist(providerEntry.Selected, idEntry.Text) + controller.Instance.Playlists().Add(providerEntry.Selected, idEntry.Text) PlaylistManager.Playlists.Refresh() PlaylistManager.PlaylistMedia.Refresh() } @@ -75,7 +71,7 @@ func createPlaylists() fyne.CanvasObject { dia.Show() }) PlaylistManager.RemoveBtn = widget.NewButton(i18n.T("gui.playlist.button.remove"), func() { - controller.RemovePlaylist(PlaylistManager.Index) + controller.Instance.Playlists().Remove(PlaylistManager.Index) //PlaylistManager.Index = 0 PlaylistManager.Playlists.Select(0) PlaylistManager.Playlists.Refresh() @@ -99,13 +95,13 @@ func createPlaylistMedias() fyne.CanvasObject { PlaylistManager.RefreshBtn = createAsyncButton( widget.NewButtonWithIcon(i18n.T("gui.playlist.button.refresh"), theme.ViewRefreshIcon(), nil), func() { - controller.PreparePlaylistByIndex(PlaylistManager.Index) + showDialogIfError(controller.Instance.Playlists().PreparePlaylistByIndex(PlaylistManager.Index)) PlaylistManager.PlaylistMedia.Refresh() }) PlaylistManager.SetAsSystemBtn = createAsyncButton( widget.NewButton(i18n.T("gui.playlist.button.set_as_system"), nil), func() { - controller.SetSystemPlaylist(PlaylistManager.Index) + showDialogIfError(controller.Instance.Playlists().SetDefault(PlaylistManager.Index)) PlaylistManager.PlaylistMedia.Refresh() PlaylistManager.UpdateCurrentSystemPlaylist() }) @@ -113,10 +109,10 @@ func createPlaylistMedias() fyne.CanvasObject { PlaylistManager.UpdateCurrentSystemPlaylist() PlaylistManager.PlaylistMedia = widget.NewList( func() int { - if len(controller.PlaylistManager) == 0 { + if controller.Instance.Playlists().Size() == 0 { return 0 } - return controller.PlaylistManager[PlaylistManager.Index].Size() + return controller.Instance.Playlists().Get(PlaylistManager.Index).Size() }, func() fyne.CanvasObject { return container.NewBorder(nil, nil, @@ -130,18 +126,19 @@ func createPlaylistMedias() fyne.CanvasObject { newLabelWithWrapping("artist", fyne.TextTruncate))) }, func(id widget.ListItemID, object fyne.CanvasObject) { - m := controller.PlaylistManager[PlaylistManager.Index].Playlist[id] + m := controller.Instance.Playlists().Get(PlaylistManager.Index).Get(id).Copy() object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*widget.Label).SetText( m.Title) object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText( m.Artist) object.(*fyne.Container).Objects[1].(*widget.Label).SetText(fmt.Sprintf("%d", id)) btns := object.(*fyne.Container).Objects[2].(*fyne.Container).Objects + m.User = controller.SystemUser btns[0].(*widget.Button).OnTapped = func() { - controller.Play(controller.ToSystemMedia(m)) + controller.Instance.PlayControl().Play(m) } btns[1].(*widget.Button).OnTapped = func() { - controller.UserPlaylist.Push(controller.ToSystemMedia(m)) + controller.Instance.Playlists().GetCurrent().Push(m) } }) return container.NewBorder( diff --git a/gui/room_bar.go b/gui/room_bar.go index 74dde19..fb64ada 100644 --- a/gui/room_bar.go +++ b/gui/room_bar.go @@ -1,9 +1,9 @@ package gui import ( + "AynaLivePlayer/common/event" + "AynaLivePlayer/common/i18n" "AynaLivePlayer/controller" - "AynaLivePlayer/event" - "AynaLivePlayer/i18n" "AynaLivePlayer/liveclient" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" @@ -27,14 +27,14 @@ var RoomTab = &struct { func createRoomSelector() fyne.CanvasObject { RoomTab.Rooms = widget.NewList( func() int { - return controller.LiveRoomManager.Size() + return controller.Instance.LiveRooms().Size() }, func() fyne.CanvasObject { return widget.NewLabel("AAAAAAAAAAAAAAAA") }, func(id widget.ListItemID, object fyne.CanvasObject) { object.(*widget.Label).SetText( - controller.LiveRoomManager.LiveRooms[id].Title()) + controller.Instance.LiveRooms().Get(id).Title()) }) RoomTab.AddBtn = widget.NewButton(i18n.T("gui.room.button.add"), func() { clientNameEntry := widget.NewSelect(liveclient.GetAllClientNames(), nil) @@ -55,7 +55,7 @@ func createRoomSelector() fyne.CanvasObject { ), func(b bool) { if b && len(clientNameEntry.Selected) > 0 && len(idEntry.Text) > 0 { - _, err := controller.LiveRoomManager.AddRoom(clientNameEntry.Selected, idEntry.Text) + _, err := controller.Instance.LiveRooms().AddRoom(clientNameEntry.Selected, idEntry.Text) if err != nil { dialog.ShowError(err, MainWindow) return @@ -69,7 +69,7 @@ func createRoomSelector() fyne.CanvasObject { dia.Show() }) RoomTab.RemoveBtn = widget.NewButton(i18n.T("gui.room.button.remove"), func() { - if err := controller.LiveRoomManager.DeleteRoom(PlaylistManager.Index); err != nil { + if err := controller.Instance.LiveRooms().DeleteRoom(PlaylistManager.Index); err != nil { dialog.ShowError(err, MainWindow) return } @@ -77,13 +77,13 @@ func createRoomSelector() fyne.CanvasObject { RoomTab.Rooms.Refresh() }) RoomTab.Rooms.OnSelected = func(id widget.ListItemID) { - rom := controller.LiveRoomManager.GetRoom(PlaylistManager.Index) + rom := controller.Instance.LiveRooms().Get(PlaylistManager.Index) if rom != nil { - rom.Client().Handler().Unregister("gui.liveroom.status") + rom.EventManager().Unregister("gui.liveroom.status") } RoomTab.Index = id - rom = controller.LiveRoomManager.GetRoom(RoomTab.Index) - rom.Client().Handler().RegisterA(liveclient.EventStatusChange, "gui.liveroom.status", func(event *event.Event) { + rom = controller.Instance.LiveRooms().Get(RoomTab.Index) + rom.EventManager().RegisterA(liveclient.EventStatusChange, "gui.liveroom.status", func(event *event.Event) { d := event.Data.(liveclient.StatusChangeEvent) if d.Connected { RoomTab.Status.SetText(i18n.T("gui.room.status.connected")) @@ -93,8 +93,8 @@ func createRoomSelector() fyne.CanvasObject { RoomTab.Status.Refresh() }) RoomTab.RoomTitle.SetText(rom.Title()) - RoomTab.AutoConnect.SetChecked(rom.AutoConnect) - if rom.Client().Status() { + RoomTab.AutoConnect.SetChecked(rom.Model().AutoConnect) + if controller.Instance.LiveRooms().GetRoomStatus(RoomTab.Index) { RoomTab.Status.SetText(i18n.T("gui.room.status.connected")) } else { RoomTab.Status.SetText(i18n.T("gui.room.status.disconnected")) @@ -115,19 +115,19 @@ func createRoomController() fyne.CanvasObject { RoomTab.ConnectBtn = widget.NewButton(i18n.T("gui.room.btn.connect"), func() { RoomTab.ConnectBtn.Disable() go func() { - _ = controller.LiveRoomManager.ConnectRoom(RoomTab.Index) + _ = controller.Instance.LiveRooms().Connect(RoomTab.Index) RoomTab.ConnectBtn.Enable() }() }) RoomTab.DisConnectBtn = widget.NewButton(i18n.T("gui.room.btn.disconnect"), func() { - _ = controller.LiveRoomManager.DisconnectRoom(RoomTab.Index) + _ = controller.Instance.LiveRooms().Disconnect(RoomTab.Index) }) RoomTab.Status = widget.NewLabel(i18n.T("gui.room.waiting")) RoomTab.RoomTitle = widget.NewLabel("") RoomTab.AutoConnect = widget.NewCheck(i18n.T("gui.room.check.autoconnect"), func(b bool) { - rom := controller.LiveRoomManager.GetRoom(RoomTab.Index) + rom := controller.Instance.LiveRooms().Get(RoomTab.Index) if rom != nil { - rom.AutoConnect = b + rom.Model().AutoConnect = b } return }) diff --git a/gui/search_bar.go b/gui/search_bar.go index 96322ce..dd5434b 100644 --- a/gui/search_bar.go +++ b/gui/search_bar.go @@ -1,10 +1,9 @@ package gui import ( - "AynaLivePlayer/config" + "AynaLivePlayer/common/i18n" "AynaLivePlayer/controller" - "AynaLivePlayer/i18n" - "AynaLivePlayer/player" + "AynaLivePlayer/model" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/widget" @@ -14,7 +13,7 @@ var SearchBar = &struct { Input *widget.Entry Button *widget.Button UseSource *widget.CheckGroup - Items []*player.Media + Items []*model.Media }{} func createSearchBar() fyne.CanvasObject { @@ -26,18 +25,18 @@ func createSearchBar() fyne.CanvasObject { s := make([]string, len(SearchBar.UseSource.Selected)) copy(s, SearchBar.UseSource.Selected) - items := make([]*player.Media, 0) + items := make([]*model.Media, 0) for _, p := range s { - if r, err := controller.SearchWithProvider(keyword, p); err == nil { + if r, err := controller.Instance.Provider().SearchWithProvider(keyword, p); err == nil { items = append(items, r...) } } - controller.ApplyUser(items, player.SystemUser) + controller.ApplyUser(items, controller.SystemUser) SearchResult.Items = items SearchResult.List.Refresh() }) - s := make([]string, len(config.Provider.Priority)) - copy(s, config.Provider.Priority) + s := make([]string, len(controller.Instance.Provider().GetPriority())) + copy(s, controller.Instance.Provider().GetPriority()) SearchBar.UseSource = widget.NewCheckGroup(s, nil) SearchBar.UseSource.Horizontal = true diff --git a/gui/search_list.go b/gui/search_list.go index 13b2f62..3ae40ae 100644 --- a/gui/search_list.go +++ b/gui/search_list.go @@ -1,10 +1,9 @@ package gui import ( + "AynaLivePlayer/common/i18n" "AynaLivePlayer/controller" - "AynaLivePlayer/i18n" - "AynaLivePlayer/player" - "AynaLivePlayer/provider" + "AynaLivePlayer/model" "fmt" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" @@ -14,9 +13,9 @@ import ( var SearchResult = &struct { List *widget.List - Items []*player.Media + Items []*model.Media }{ - Items: []*player.Media{}, + Items: []*model.Media{}, } func createSearchList() fyne.CanvasObject { @@ -42,14 +41,14 @@ func createSearchList() fyne.CanvasObject { object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText( SearchResult.Items[id].Artist) object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[2].(*widget.Label).SetText( - SearchResult.Items[id].Meta.(provider.Meta).Name) + SearchResult.Items[id].Meta.(model.Meta).Name) object.(*fyne.Container).Objects[1].(*widget.Label).SetText(fmt.Sprintf("%d", id)) btns := object.(*fyne.Container).Objects[2].(*fyne.Container).Objects btns[0].(*widget.Button).OnTapped = func() { - controller.Play(SearchResult.Items[id]) + controller.Instance.PlayControl().Play(SearchResult.Items[id]) } btns[1].(*widget.Button).OnTapped = func() { - controller.UserPlaylist.Push(SearchResult.Items[id]) + controller.Instance.Playlists().GetCurrent().Push(SearchResult.Items[id]) } }) return container.NewBorder( diff --git a/liveclient/bilibili.go b/liveclient/bilibili.go index 0099dde..f53388c 100644 --- a/liveclient/bilibili.go +++ b/liveclient/bilibili.go @@ -1,8 +1,8 @@ package liveclient import ( - "AynaLivePlayer/event" - "AynaLivePlayer/logger" + "AynaLivePlayer/common/event" + "AynaLivePlayer/common/logger" "errors" "github.com/aynakeya/blivedm" "github.com/sirupsen/logrus" @@ -11,10 +11,10 @@ import ( ) type Bilibili struct { - client *blivedm.BLiveWsClient - handlers *event.Handler - roomName string - status bool + client *blivedm.BLiveWsClient + eventManager *event.Manager + roomName string + status bool } func init() { @@ -29,14 +29,14 @@ func init() { func NewBilibili(roomId int) LiveClient { cl := &Bilibili{ - client: &blivedm.BLiveWsClient{ShortId: roomId, Account: blivedm.DanmuAccount{UID: 0}, HearbeatInterval: 10 * time.Second}, - handlers: event.NewHandler(), - roomName: "Unknown", + client: &blivedm.BLiveWsClient{ShortId: roomId, Account: blivedm.DanmuAccount{UID: 0}, HearbeatInterval: 10 * time.Second}, + eventManager: event.MainManager.NewChildManager(), + roomName: "Unknown", } cl.client.OnDisconnect = func(client *blivedm.BLiveWsClient) { cl.l().Warn("disconnect from websocket connection, maybe try reconnect") cl.status = false - cl.Handler().CallA(EventStatusChange, StatusChangeEvent{Connected: false, Client: cl}) + cl.eventManager.CallA(EventStatusChange, StatusChangeEvent{Connected: false, Client: cl}) } cl.client.RegHandler(blivedm.CmdDanmaku, cl.handleMsg) return cl @@ -54,8 +54,8 @@ func (b *Bilibili) Status() bool { return b.status } -func (b *Bilibili) Handler() *event.Handler { - return b.handlers +func (b *Bilibili) EventManager() *event.Manager { + return b.eventManager } func (b *Bilibili) Connect() bool { @@ -66,7 +66,7 @@ func (b *Bilibili) Connect() bool { if b.client.InitRoom() && b.client.ConnectDanmuServer() { b.roomName = b.client.RoomInfo.Title b.status = true - b.Handler().CallA(EventStatusChange, StatusChangeEvent{Connected: true, Client: b}) + b.eventManager.CallA(EventStatusChange, StatusChangeEvent{Connected: true, Client: b}) b.l().Info("Connect Success") return true } @@ -80,7 +80,7 @@ func (b *Bilibili) Disconnect() bool { return true } b.client.Disconnect() - b.Handler().CallA(EventStatusChange, StatusChangeEvent{Connected: false, Client: b}) + b.eventManager.CallA(EventStatusChange, StatusChangeEvent{Connected: false, Client: b}) return true } @@ -113,7 +113,7 @@ func (b *Bilibili) handleMsg(context *blivedm.Context) { } b.l().Debug("receive message", dmsg) go func() { - b.handlers.Call(&event.Event{ + b.eventManager.Call(&event.Event{ Id: EventMessageReceive, Cancelled: false, Data: &dmsg, diff --git a/liveclient/bilibili_test.go b/liveclient/bilibili_test.go index 5719437..6b28ee4 100644 --- a/liveclient/bilibili_test.go +++ b/liveclient/bilibili_test.go @@ -1,8 +1,8 @@ package liveclient import ( - "AynaLivePlayer/event" - "AynaLivePlayer/logger" + "AynaLivePlayer/common/event" + "AynaLivePlayer/common/logger" "fmt" "github.com/sirupsen/logrus" "testing" @@ -11,9 +11,9 @@ import ( func TestBilibili_Client(t *testing.T) { logger.Logger.SetLevel(logrus.DebugLevel) - lc := NewBilibili(7777) + lc := NewBilibili(7777, event.NewManger()) //lc := NewBilibili(8524916587) - lc.Handler().Register(&event.EventHandler{ + lc.Handler().Register(&event.Handler{ EventId: EventMessageReceive, Name: "test.receivemsg", Handler: func(event *event.Event) { diff --git a/liveclient/event.go b/liveclient/event.go index 8a44dc0..696082a 100644 --- a/liveclient/event.go +++ b/liveclient/event.go @@ -1,7 +1,7 @@ package liveclient import ( - "AynaLivePlayer/event" + "AynaLivePlayer/common/event" ) const ( diff --git a/liveclient/liveclient.go b/liveclient/liveclient.go index bf47704..935e2b1 100644 --- a/liveclient/liveclient.go +++ b/liveclient/liveclient.go @@ -1,7 +1,7 @@ package liveclient import ( - "AynaLivePlayer/event" + "AynaLivePlayer/common/event" "errors" ) @@ -24,14 +24,13 @@ type DanmuMessage struct { User DanmuUser Message string } - type LiveClient interface { ClientName() string RoomName() string Connect() bool Disconnect() bool Status() bool - Handler() *event.Handler + EventManager() *event.Manager } type LiveClientCtor func(id string) (LiveClient, error) diff --git a/player/event.go b/model/event.go similarity index 61% rename from player/event.go rename to model/event.go index 7faaa22..b021fd4 100644 --- a/player/event.go +++ b/model/event.go @@ -1,7 +1,7 @@ -package player +package model import ( - "AynaLivePlayer/event" + "AynaLivePlayer/common/event" ) const ( @@ -13,6 +13,10 @@ const ( EventLyricReload event.EventId = "lyric.reload" ) +func EventPlayerPropertyUpdate(property PlayerProperty) event.EventId { + return event.EventId("player.property.update." + string(property)) +} + type PlaylistInsertEvent struct { Playlist *Playlist Index int @@ -20,13 +24,7 @@ type PlaylistInsertEvent struct { } type PlaylistUpdateEvent struct { - Playlist *Playlist -} - -func newPlaylistUpdateEvent(playlist *Playlist) PlaylistUpdateEvent { - return PlaylistUpdateEvent{ - Playlist: playlist, - } + Playlist *Playlist // Playlist is a copy of the playlist } type PlayEvent struct { @@ -42,3 +40,13 @@ type LyricUpdateEvent struct { type LyricReloadEvent struct { Lyrics *Lyric } + +type PlayerPropertyUpdateEvent struct { + Property PlayerProperty + Value PlayerPropertyValue +} + +type LiveRoomStatusUpdateEvent struct { + RoomTitle string + Status bool +} diff --git a/model/liveroom.go b/model/liveroom.go new file mode 100644 index 0000000..d9033b6 --- /dev/null +++ b/model/liveroom.go @@ -0,0 +1,17 @@ +package model + +import "fmt" + +type LiveRoom struct { + ClientName string + ID string + AutoConnect bool +} + +func (r *LiveRoom) String() string { + return fmt.Sprintf("", r.ClientName, r.ID) +} + +func (r *LiveRoom) Title() string { + return fmt.Sprintf("%s-%s", r.ClientName, r.ID) +} diff --git a/player/lyric.go b/model/lyric.go similarity index 72% rename from player/lyric.go rename to model/lyric.go index 94fb3b0..a032d0e 100644 --- a/player/lyric.go +++ b/model/lyric.go @@ -1,7 +1,6 @@ -package player +package model import ( - "AynaLivePlayer/event" "github.com/spf13/cast" "regexp" "sort" @@ -23,12 +22,10 @@ type LyricContext struct { } type Lyric struct { - Lyrics []*LyricLine - Handler *event.Handler - prev float64 + Lyrics []*LyricLine } -func (l *Lyric) Reload(lyric string) { +func LoadLyric(lyric string) *Lyric { tmp := make(map[float64]*LyricLine) times := make([]float64, 0) for _, line := range strings.Split(lyric, "\n") { @@ -55,26 +52,7 @@ func (l *Lyric) Reload(lyric string) { Time: 99999999999, Lyric: "", }) - l.Lyrics = lrcs - l.Handler.CallA(EventLyricReload, LyricReloadEvent{Lyrics: l}) - return -} - -func (l *Lyric) Update(time float64) { - lrc := l.Find(time) - if lrc == nil { - return - } - if l.prev == lrc.Time { - return - } - l.prev = lrc.Time - l.Handler.CallA(EventLyricUpdate, LyricUpdateEvent{ - Lyrics: l, - Time: time, - Lyric: lrc, - }) - return + return &Lyric{Lyrics: lrcs} } func (l *Lyric) Find(time float64) *LyricLine { @@ -95,7 +73,6 @@ func (l *Lyric) FindContext(time float64, prev int, next int) *LyricContext { if (i + 1 + next) > len(l.Lyrics) { next = len(l.Lyrics) - i - 1 } - //l.Lyrics[i+prev : i+1+next] return &LyricContext{ Current: l.Lyrics[i], Prev: l.Lyrics[i+prev : i], @@ -105,9 +82,3 @@ func (l *Lyric) FindContext(time float64, prev int, next int) *LyricContext { } return nil } - -func NewLyric(lyric string) *Lyric { - l := &Lyric{Handler: event.NewHandler(), prev: -1} - l.Reload(lyric) - return l -} diff --git a/player/lyric_test.go b/model/lyric_test.go similarity index 93% rename from player/lyric_test.go rename to model/lyric_test.go index 72ba0b9..60dbc6c 100644 --- a/player/lyric_test.go +++ b/model/lyric_test.go @@ -1,4 +1,4 @@ -package player +package model import ( "fmt" @@ -8,16 +8,16 @@ import ( var testLyric = "[ti:双截棍]\n[ar:周杰伦]\n[al:范特西]\n[00:03.85]双截棍\n[00:07.14]\n[00:30.13]岩烧店的烟味弥漫隔壁是国术馆\n[00:32.57]店里面的妈妈桑茶道有三段\n[00:34.61]教拳脚武术的老板练铁沙掌耍杨家枪\n[00:37.34]硬底子功夫最擅长还会金钟罩铁步衫\n[00:39.67]他们儿子我习惯从小就耳濡目染\n[00:41.96]什么刀枪跟棍棒我都耍的有模有样\n[00:44.22]什么兵器最喜欢双截棍柔中带刚\n[00:46.73]想要去河南嵩山学少林跟武当\n[00:49.24]干什么(客)干什么(客)呼吸吐纳心自在\n[00:51.28]干什么(客)干什么(客)气沉丹田手心开\n[00:53.44]干什么(客)干什么(客)日行千里系沙袋\n[00:56.13]飞檐走壁莫奇怪去去就来\n[00:58.35]一个马步向前一记左钩拳右钩拳\n[01:01.26]一句惹毛我的人有危险一再重演\n[01:04.02]一根我不抽的菸一放好多年它一直在身边\n[01:07.28]干什么(客)干什么(客)我打开任督二脉\n[01:10.27]干什么(客)干什么(客)东亚病夫的招牌\n[01:12.75]干什么(客)干什么(客)已被我一脚踢开\n[02:32.62][01:54.69][01:15.40]快使用双截棍哼哼哈兮\n[02:34.52][01:56.63][01:18.40]快使用双截棍哼哼哈兮\n[02:36.88][01:58.98][01:20.71]习武之人切记仁者无敌\n[02:39.45][02:01.66][01:23.27]是谁在练太极风生水起\n[02:41.97][02:03.93][01:25.74]快使用双截棍哼哼哈兮\n[02:44.42][02:06.11][01:27.75]快使用双截棍哼哼哈兮\n[02:47.01][02:08.54][01:30.13]如果我有轻功飞檐走壁\n[02:49.36][02:11.03][01:32.67]为人耿直不屈一身正气\n[02:53.81]快使用双截棍哼\n[02:56.30]我用手刀防御哼\n[02:58.52]漂亮的回旋踢\n[02:59.52]" func TestLyric(t *testing.T) { - lryic := NewLyric(testLyric) + lryic := LoadLyric(testLyric) for _, lrc := range lryic.Lyrics { fmt.Println(lrc) } } func TestLyricFind(t *testing.T) { - lryic := NewLyric(testLyric) + lryic := LoadLyric(testLyric) fmt.Println(lryic.Find(90.4)) - for _, l := range lryic.FindContext(90.4, -2, 2) { + for _, l := range lryic.FindContext(90.4, -2, 2).Next { fmt.Println(l) } } diff --git a/player/media.go b/model/media.go similarity index 98% rename from player/media.go rename to model/media.go index 2dc8863..c1f047e 100644 --- a/player/media.go +++ b/model/media.go @@ -1,4 +1,4 @@ -package player +package model import ( "AynaLivePlayer/liveclient" diff --git a/player/media_test.go b/model/media_test.go similarity index 96% rename from player/media_test.go rename to model/media_test.go index 404c018..cec1694 100644 --- a/player/media_test.go +++ b/model/media_test.go @@ -1,4 +1,4 @@ -package player +package model import ( "fmt" diff --git a/model/player.go b/model/player.go new file mode 100644 index 0000000..5bd32a9 --- /dev/null +++ b/model/player.go @@ -0,0 +1,18 @@ +package model + +type AudioDevice struct { + Name string + Description string +} + +type PlayerPropertyValue any +type PlayerProperty string + +const ( + PlayerPropIdleActive PlayerProperty = "idle-active" + PlayerPropTimePos PlayerProperty = "time-pos" + PlayerPropDuration PlayerProperty = "duration" + PlayerPropPercentPos PlayerProperty = "percent-pos" + PlayerPropPause PlayerProperty = "pause" + PlayerPropVolume PlayerProperty = "volume" +) diff --git a/model/playlist.go b/model/playlist.go new file mode 100644 index 0000000..43bfdbf --- /dev/null +++ b/model/playlist.go @@ -0,0 +1,36 @@ +package model + +import "fmt" + +type PlaylistMode int + +const ( + PlaylistModeNormal PlaylistMode = iota + PlaylistModeRandom +) + +type Playlist struct { + Name string + Medias []*Media + Mode PlaylistMode + Meta Meta +} + +func (p Playlist) String() string { + return fmt.Sprintf("", p.Name) +} + +func (p *Playlist) Size() int { + return len(p.Medias) +} + +func (p *Playlist) Copy() *Playlist { + medias := make([]*Media, len(p.Medias)) + copy(medias, p.Medias) + return &Playlist{ + Name: p.Name, + Medias: medias, + Mode: p.Mode, + Meta: p.Meta, + } +} diff --git a/model/provider.go b/model/provider.go new file mode 100644 index 0000000..4a6e626 --- /dev/null +++ b/model/provider.go @@ -0,0 +1,6 @@ +package model + +type Meta struct { + Name string + Id string +} diff --git a/model/user.go b/model/user.go new file mode 100644 index 0000000..d048e34 --- /dev/null +++ b/model/user.go @@ -0,0 +1,5 @@ +package model + +type User struct { + Name string +} diff --git a/player/player.go b/player/player.go index 0d3dabd..d47533d 100644 --- a/player/player.go +++ b/player/player.go @@ -1,187 +1,24 @@ package player import ( - "AynaLivePlayer/event" - "AynaLivePlayer/logger" - "AynaLivePlayer/util" - "github.com/aynakeya/go-mpv" - "github.com/sirupsen/logrus" - "github.com/tidwall/gjson" + "AynaLivePlayer/common/event" + "AynaLivePlayer/common/logger" + "AynaLivePlayer/model" ) -const MODULE_PLAYER = "Player.Player" +var lg = logger.Logger.WithField("Module", "PlayControl") -type PropertyHandlerFunc func(property *mpv.EventProperty) - -type Player struct { - running bool - libmpv *mpv.Mpv - Playing *Media - PropertyHandler map[string][]PropertyHandlerFunc - EventHandler *event.Handler -} - -func NewPlayer() *Player { - player := &Player{ - running: true, - libmpv: mpv.Create(), - PropertyHandler: make(map[string][]PropertyHandlerFunc), - EventHandler: event.NewHandler(), - } - err := player.libmpv.Initialize() - if err != nil { - player.l().Error("initialize libmpv failed") - return nil - } - player.libmpv.SetOptionString("vo", "null") - player.l().Info("initialize libmpv success") - return player -} - -func (p *Player) Start() { - p.l().Info("starting mpv player") - go func() { - for p.running { - e := p.libmpv.WaitEvent(1) - if e == nil { - p.l().Warn("event loop got nil event") - } - p.l().Trace("new event", e) - if e.EventId == mpv.EVENT_PROPERTY_CHANGE { - property := e.Property() - p.l().Trace("receive property change event", property) - for _, handler := range p.PropertyHandler[property.Name] { - // todo: @3 - go handler(&property) - } - } - if e.EventId == mpv.EVENT_SHUTDOWN { - p.l().Info("libmpv shutdown") - p.Stop() - } - } - }() -} - -func (p *Player) Stop() { - p.l().Info("stopping mpv player") - p.running = false - p.libmpv.TerminateDestroy() -} - -func (p *Player) l() *logrus.Entry { - return logger.Logger.WithField("Module", MODULE_PLAYER) -} - -func (p *Player) Play(media *Media) error { - p.l().Infof("Play media %s", media.Url) - if val, ok := media.Header["User-Agent"]; ok { - p.l().Debug("set user-agent for mpv player") - err := p.libmpv.SetPropertyString("user-agent", val) - if err != nil { - p.l().Warn("set player user-agent failed", err) - return err - } - } - - if val, ok := media.Header["Referer"]; ok { - p.l().Debug("set referrer for mpv player") - err := p.libmpv.SetPropertyString("referrer", val) - if err != nil { - p.l().Warn("set player referrer failed", err) - return err - } - } - p.l().Debugf("mpv command load file %s %s", media.Title, media.Url) - if err := p.libmpv.Command([]string{"loadfile", media.Url}); err != nil { - p.l().Warn("mpv load media failed", media) - return err - } - p.Playing = media - p.EventHandler.CallA(EventPlay, PlayEvent{Media: media}) - return nil -} - -func (p *Player) IsPaused() bool { - property, err := p.libmpv.GetProperty("pause", mpv.FORMAT_FLAG) - if err != nil { - p.l().Warn("get property pause failed", err) - return false - } - return property.(bool) -} - -func (p *Player) Pause() error { - p.l().Tracef("pause") - return p.libmpv.SetProperty("pause", mpv.FORMAT_FLAG, true) -} - -func (p *Player) Unpause() error { - p.l().Tracef("unpause") - return p.libmpv.SetProperty("pause", mpv.FORMAT_FLAG, false) -} - -// SetVolume set mpv volume, from 0.0 - 100.0 -func (p *Player) SetVolume(volume float64) error { - p.l().Tracef("set volume to %f", volume) - return p.libmpv.SetProperty("volume", mpv.FORMAT_DOUBLE, volume) -} - -func (p *Player) IsIdle() bool { - property, err := p.libmpv.GetProperty("idle-active", mpv.FORMAT_FLAG) - if err != nil { - p.l().Warn("get property idle-active failed", err) - return false - } - return property.(bool) -} - -// Seek change position for current file -// absolute = true : position is the time in second -// absolute = false: position is in percentage eg 0.1 0.2 -func (p *Player) Seek(position float64, absolute bool) error { - p.l().Tracef("seek to %f (absolute=%t)", position, absolute) - if absolute { - return p.libmpv.SetProperty("time-pos", mpv.FORMAT_DOUBLE, position) - } else { - return p.libmpv.SetProperty("percent-pos", mpv.FORMAT_DOUBLE, position) - } -} - -func (p *Player) ObserveProperty(property string, handler ...PropertyHandlerFunc) error { - p.l().Trace("add property observer for mpv") - p.PropertyHandler[property] = append(p.PropertyHandler[property], handler...) - if len(p.PropertyHandler[property]) == 1 { - return p.libmpv.ObserveProperty(util.Hash64(property), property, mpv.FORMAT_NODE) - } - return nil -} - -type AudioDevice struct { - Name string - Description string -} - -// GetAudioDeviceList get output device for mpv -// return format is []AudioDevice -func (p *Player) GetAudioDeviceList() ([]AudioDevice, error) { - p.l().Trace("getting audio device list for mpv") - property, err := p.libmpv.GetProperty("audio-device-list", mpv.FORMAT_STRING) - if err != nil { - return nil, err - } - dl := make([]AudioDevice, 0) - gjson.Parse(property.(string)).ForEach(func(key, value gjson.Result) bool { - dl = append(dl, AudioDevice{ - Name: value.Get("name").String(), - Description: value.Get("description").String(), - }) - return true - }) - return dl, nil -} - -func (p *Player) SetAudioDevice(device string) error { - p.l().Tracef("set audio device %s for mpv", device) - return p.libmpv.SetPropertyString("audio-device", device) +type IPlayer interface { + Start() + Stop() + Play(media *model.Media) error + IsPaused() bool + Pause() error + Unpause() error + SetVolume(volume float64) error + IsIdle() bool + Seek(position float64, absolute bool) error + ObserveProperty(property model.PlayerProperty, name string, handler event.HandlerFunc) error + GetAudioDeviceList() ([]model.AudioDevice, error) + SetAudioDevice(device string) error } diff --git a/player/player_mpv.go b/player/player_mpv.go new file mode 100644 index 0000000..4dc132d --- /dev/null +++ b/player/player_mpv.go @@ -0,0 +1,204 @@ +package player + +import ( + "AynaLivePlayer/common/event" + "AynaLivePlayer/common/util" + "AynaLivePlayer/model" + "github.com/aynakeya/go-mpv" + "github.com/tidwall/gjson" +) + +var mpvPropertyMap = map[model.PlayerProperty]string{ + model.PlayerPropDuration: "duration", + model.PlayerPropTimePos: "time-pos", + model.PlayerPropIdleActive: "idle-active", + model.PlayerPropPercentPos: "percent-pos", + model.PlayerPropPause: "pause", + model.PlayerPropVolume: "volume", +} + +var mpvPropertyMapInv = map[string]model.PlayerProperty{ + "duration": model.PlayerPropDuration, + "time-pos": model.PlayerPropTimePos, + "idle-active": model.PlayerPropIdleActive, + "percent-pos": model.PlayerPropPercentPos, + "pause": model.PlayerPropPause, + "volume": model.PlayerPropVolume, +} + +type MpvPlayer struct { + running bool + libmpv *mpv.Mpv + Playing *model.Media + propertyWatchedFlag map[model.PlayerProperty]int + eventManager *event.Manager +} + +func NewMpvPlayer() IPlayer { + player := &MpvPlayer{ + running: true, + libmpv: mpv.Create(), + propertyWatchedFlag: make(map[model.PlayerProperty]int), + eventManager: event.MainManager.NewChildManager(), + } + err := player.libmpv.Initialize() + if err != nil { + lg.Error("[MPV PlayControl] initialize libmpv failed") + return nil + } + _ = player.libmpv.SetOptionString("vo", "null") + lg.Info("[MPV PlayControl] initialize libmpv success") + player.Start() + return player +} + +func (p *MpvPlayer) Start() { + lg.Info("[MPV PlayControl] starting mpv player") + go func() { + for p.running { + e := p.libmpv.WaitEvent(1) + if e == nil { + lg.Warn("[MPV PlayControl] event loop got nil event") + } + lg.Trace("[MPV PlayControl] new event", e) + if e.EventId == mpv.EVENT_PROPERTY_CHANGE { + eventProperty := e.Property() + property, ok := mpvPropertyMapInv[eventProperty.Name] + if !ok { + continue + } + var value interface{} = nil + if eventProperty.Data != nil { + value = eventProperty.Data.(mpv.Node).Value + } + p.eventManager.CallA( + model.EventPlayerPropertyUpdate(property), + model.PlayerPropertyUpdateEvent{ + Property: property, + Value: value, + }) + + } + if e.EventId == mpv.EVENT_SHUTDOWN { + lg.Info("[MPV PlayControl] libmpv shutdown") + p.Stop() + } + } + }() +} + +func (p *MpvPlayer) Stop() { + lg.Info("[MPV PlayControl] stopping mpv player") + p.running = false + p.libmpv.TerminateDestroy() +} + +func (p *MpvPlayer) Play(media *model.Media) error { + lg.Infof("[MPV PlayControl] Play media %s", media.Url) + if val, ok := media.Header["User-Agent"]; ok { + lg.Debug("[MPV PlayControl] set user-agent for mpv player") + err := p.libmpv.SetPropertyString("user-agent", val) + if err != nil { + lg.Warn("[MPV PlayControl] set player user-agent failed", err) + return err + } + } + + if val, ok := media.Header["Referer"]; ok { + lg.Debug("[MPV PlayControl] set referrer for mpv player") + err := p.libmpv.SetPropertyString("referrer", val) + if err != nil { + lg.Warn("[MPV PlayControl] set player referrer failed", err) + return err + } + } + lg.Debugf("mpv command load file %s %s", media.Title, media.Url) + if err := p.libmpv.Command([]string{"loadfile", media.Url}); err != nil { + lg.Warn("[MPV PlayControl] mpv load media failed", media) + return err + } + p.Playing = media + return nil +} + +func (p *MpvPlayer) IsPaused() bool { + property, err := p.libmpv.GetProperty("pause", mpv.FORMAT_FLAG) + if err != nil { + lg.Warn("[MPV PlayControl] get property pause failed", err) + return false + } + return property.(bool) +} + +func (p *MpvPlayer) Pause() error { + lg.Tracef("[MPV PlayControl] pause") + return p.libmpv.SetProperty("pause", mpv.FORMAT_FLAG, true) +} + +func (p *MpvPlayer) Unpause() error { + lg.Tracef("[MPV PlayControl] unpause") + return p.libmpv.SetProperty("pause", mpv.FORMAT_FLAG, false) +} + +// SetVolume set mpv volume, from 0.0 - 100.0 +func (p *MpvPlayer) SetVolume(volume float64) error { + lg.Tracef("[MPV PlayControl] set volume to %f", volume) + return p.libmpv.SetProperty("volume", mpv.FORMAT_DOUBLE, volume) +} + +func (p *MpvPlayer) IsIdle() bool { + property, err := p.libmpv.GetProperty("idle-active", mpv.FORMAT_FLAG) + if err != nil { + lg.Warn("[MPV PlayControl] get property idle-active failed", err) + return false + } + return property.(bool) +} + +// Seek change position for current file +// absolute = true : position is the time in second +// absolute = false: position is in percentage eg 0.1 0.2 +func (p *MpvPlayer) Seek(position float64, absolute bool) error { + lg.Tracef("[MPV PlayControl] seek to %f (absolute=%t)", position, absolute) + if absolute { + return p.libmpv.SetProperty("time-pos", mpv.FORMAT_DOUBLE, position) + } else { + return p.libmpv.SetProperty("percent-pos", mpv.FORMAT_DOUBLE, position) + } +} + +func (p *MpvPlayer) ObserveProperty(property model.PlayerProperty, name string, handler event.HandlerFunc) error { + lg.Trace("[MPV PlayControl] add property observer for mpv") + p.eventManager.RegisterA( + model.EventPlayerPropertyUpdate(property), + name, handler) + if _, ok := p.propertyWatchedFlag[property]; !ok { + p.propertyWatchedFlag[property] = 1 + return p.libmpv.ObserveProperty(util.Hash64(mpvPropertyMap[property]), mpvPropertyMap[property], mpv.FORMAT_NODE) + } + return nil +} + +// GetAudioDeviceList get output device for mpv +// return format is []AudioDevice +func (p *MpvPlayer) GetAudioDeviceList() ([]model.AudioDevice, error) { + lg.Trace("[MPV PlayControl] getting audio device list for mpv") + property, err := p.libmpv.GetProperty("audio-device-list", mpv.FORMAT_STRING) + if err != nil { + return nil, err + } + dl := make([]model.AudioDevice, 0) + gjson.Parse(property.(string)).ForEach(func(key, value gjson.Result) bool { + dl = append(dl, model.AudioDevice{ + Name: value.Get("name").String(), + Description: value.Get("description").String(), + }) + return true + }) + return dl, nil +} + +func (p *MpvPlayer) SetAudioDevice(device string) error { + lg.Tracef("[MPV PlayControl] set audio device %s for mpv", device) + return p.libmpv.SetPropertyString("audio-device", device) +} diff --git a/player/player_test.go b/player/player_test.go index 3e8dccd..4c800db 100644 --- a/player/player_test.go +++ b/player/player_test.go @@ -1,6 +1,7 @@ package player import ( + "AynaLivePlayer/model" "fmt" "github.com/aynakeya/go-mpv" "testing" @@ -18,7 +19,7 @@ func TestPlayer(t *testing.T) { player.ObserveProperty("percent-pos", func(property *mpv.EventProperty) { fmt.Println(2, property.Data) }) - player.Play(&Media{ + player.Play(&model.Media{ Url: "https://ia600809.us.archive.org/19/items/VillagePeopleYMCAOFFICIALMusicVideo1978/Village%20People%20-%20YMCA%20OFFICIAL%20Music%20Video%201978.mp4", }) time.Sleep(time.Second * 15) diff --git a/player/playlist.go b/player/playlist.go deleted file mode 100644 index 0768c43..0000000 --- a/player/playlist.go +++ /dev/null @@ -1,196 +0,0 @@ -package player - -import ( - "AynaLivePlayer/event" - "AynaLivePlayer/logger" - "github.com/sirupsen/logrus" - "math/rand" - "sync" - "time" -) - -const MODULE_PLAYLIST = "Player.Playlist" - -func init() { - rand.Seed(time.Now().UnixNano()) -} - -type PlaylistConfig struct { - RandomNext bool -} - -type Playlist struct { - Index int - Name string - Config PlaylistConfig - Playlist []*Media - Handler *event.Handler - Meta interface{} - Lock sync.RWMutex -} - -func NewPlaylist(name string, config PlaylistConfig) *Playlist { - return &Playlist{ - Index: 0, - Name: name, - Config: config, - Playlist: make([]*Media, 0), - Handler: event.NewHandler(), - } -} - -func (p *Playlist) l() *logrus.Entry { - return logger.Logger.WithFields(logrus.Fields{ - "Module": MODULE_PLAYLIST, - "Name": p.Name, - }) -} - -func (p *Playlist) Size() int { - p.l().Tracef("getting size=%d", len(p.Playlist)) - return len(p.Playlist) -} - -func (p *Playlist) Pop() *Media { - p.l().Infof("pop first media") - if p.Size() == 0 { - p.l().Warn("pop first media failed, no media left in the playlist") - return nil - } - p.Lock.Lock() - index := 0 - if p.Config.RandomNext { - index = rand.Intn(p.Size()) - } - media := p.Playlist[index] - for i := index; i > 0; i-- { - p.Playlist[i] = p.Playlist[i-1] - } - p.Playlist = p.Playlist[1:] - p.Lock.Unlock() - defer p.Handler.CallA(EventPlaylistUpdate, PlaylistUpdateEvent{Playlist: p}) - return media -} - -func (p *Playlist) Replace(medias []*Media) { - p.Lock.Lock() - p.Playlist = medias - p.Index = 0 - p.Lock.Unlock() - p.Handler.CallA(EventPlaylistUpdate, PlaylistUpdateEvent{Playlist: p}) - return -} - -func (p *Playlist) Push(media *Media) { - p.Insert(-1, media) - return -} - -// Insert runtime in O(n) but i don't care -func (p *Playlist) Insert(index int, media *Media) { - p.l().Infof("insert new meida to index %d", index) - p.l().Debugf("media= %s", media.Title) - e := event.Event{ - Id: EventPlaylistPreInsert, - Cancelled: false, - Data: PlaylistInsertEvent{ - Playlist: p, - Index: index, - Media: media, - }, - } - p.Handler.Call(&e) - if e.Cancelled { - p.l().Info("insert new media has been cancelled by handler") - return - } - p.Lock.Lock() - if index > p.Size() { - index = p.Size() - } - if index < 0 { - index = p.Size() + index + 1 - } - p.Playlist = append(p.Playlist, nil) - for i := p.Size() - 1; i > index; i-- { - p.Playlist[i] = p.Playlist[i-1] - } - p.Playlist[index] = media - p.Lock.Unlock() - defer func() { - p.Handler.Call(&event.Event{ - Id: EventPlaylistInsert, - Cancelled: false, - Data: PlaylistInsertEvent{ - Playlist: p, - Index: index, - Media: media, - }, - }) - p.Handler.CallA(EventPlaylistUpdate, PlaylistUpdateEvent{Playlist: p}) - }() -} - -func (p *Playlist) Delete(index int) { - p.l().Infof("from media at index %d", index) - p.Lock.Lock() - if index >= p.Size() || index < 0 { - p.l().Warnf("media at index %d does not exist", index) - p.Lock.Unlock() - return - } - // todo: @5 delete optimization - p.Playlist = append(p.Playlist[:index], p.Playlist[index+1:]...) - p.Lock.Unlock() - defer p.Handler.CallA(EventPlaylistUpdate, PlaylistUpdateEvent{Playlist: p}) -} - -func (p *Playlist) Move(src int, dest int) { - p.l().Infof("from media from index %d to %d", src, dest) - p.Lock.Lock() - if src >= p.Size() || src < 0 { - p.l().Warnf("media at index %d does not exist", src) - p.Lock.Unlock() - return - } - if dest >= p.Size() { - dest = p.Size() - 1 - } - if dest < 0 { - dest = 0 - } - if dest == src { - p.l().Warn("src and dest are same, operation not perform") - p.Lock.Unlock() - return - } - step := 1 - if dest < src { - step = -1 - } - tmp := p.Playlist[src] - for i := src; i != dest; i += step { - p.Playlist[i] = p.Playlist[i+step] - } - p.Playlist[dest] = tmp - p.Lock.Unlock() - defer p.Handler.CallA(EventPlaylistUpdate, PlaylistUpdateEvent{Playlist: p}) -} - -func (p *Playlist) Next() *Media { - p.l().Infof("get next media with random=%t", p.Config.RandomNext) - if p.Size() == 0 { - p.l().Info("get next media failed, no media left in the playlist") - return nil - } - var index int - index = p.Index - if p.Config.RandomNext { - p.Index = rand.Intn(p.Size()) - } else { - p.Index = (p.Index + 1) % p.Size() - } - p.l().Tracef("return index %d, new index %d", index, p.Index) - defer p.Handler.CallA(EventPlaylistUpdate, PlaylistUpdateEvent{Playlist: p}) - return p.Playlist[index] -} diff --git a/player/playlist_test.go b/player/playlist_test.go deleted file mode 100644 index 8d3d3a9..0000000 --- a/player/playlist_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package player - -import ( - "fmt" - "strconv" - "testing" -) - -func TestPlaylist_Insert(t *testing.T) { - pl := NewPlaylist("asdf", PlaylistConfig{RandomNext: false}) - for i := 0; i < 10; i++ { - pl.Insert(-1, &Media{Url: strconv.Itoa(i)}) - } - pl.Insert(3, &Media{Url: "a"}) - pl.Insert(0, &Media{Url: "b"}) - pl.Insert(-2, &Media{Url: "x"}) - pl.Insert(-1, &Media{Url: "h"}) - for i := 0; i < pl.Size(); i++ { - fmt.Print(pl.Playlist[i].Url, " ") - } - -} diff --git a/player/user.go b/player/user.go deleted file mode 100644 index b18615c..0000000 --- a/player/user.go +++ /dev/null @@ -1,8 +0,0 @@ -package player - -type User struct { - Name string -} - -var PlaylistUser = &User{Name: "Playlist"} -var SystemUser = &User{Name: "System"} diff --git a/plugin/diange/diange.go b/plugin/diange/diange.go index debd6b1..63f62be 100644 --- a/plugin/diange/diange.go +++ b/plugin/diange/diange.go @@ -1,12 +1,12 @@ package diange import ( + "AynaLivePlayer/common/i18n" + "AynaLivePlayer/common/logger" "AynaLivePlayer/config" "AynaLivePlayer/controller" "AynaLivePlayer/gui" - "AynaLivePlayer/i18n" "AynaLivePlayer/liveclient" - "AynaLivePlayer/logger" "fmt" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" @@ -36,9 +36,10 @@ type Diange struct { SourceCMD []string cooldowns map[string]int panel fyne.CanvasObject + contro controller.IController } -func NewDiange() *Diange { +func NewDiange(contr controller.IController) *Diange { return &Diange{ UserPermission: true, PrivilegePermission: true, @@ -48,6 +49,7 @@ func NewDiange() *Diange { CustomCMD: "add", SourceCMD: make([]string, 0), cooldowns: make(map[string]int), + contro: contr, } } @@ -58,7 +60,7 @@ func (d *Diange) Name() string { func (d *Diange) Enable() error { config.LoadConfig(d) d.initCMD() - controller.AddCommand(d) + d.contro.LiveRooms().AddDanmuCommand(d) gui.AddConfigLayout(d) return nil } @@ -68,15 +70,15 @@ func (d *Diange) Disable() error { } func (d *Diange) initCMD() { - if len(d.SourceCMD) == len(config.Provider.Priority) { + if len(d.SourceCMD) == len(d.contro.Provider().GetPriority()) { return } - if len(d.SourceCMD) > len(config.Provider.Priority) { - d.SourceCMD = d.SourceCMD[:len(config.Provider.Priority)] + if len(d.SourceCMD) > len(d.contro.Provider().GetPriority()) { + d.SourceCMD = d.SourceCMD[:len(d.contro.Provider().GetPriority())] return } - for i := len(d.SourceCMD); i < len(config.Provider.Priority); i++ { - d.SourceCMD = append(d.SourceCMD, "点歌"+config.Provider.Priority[i]) + for i := len(d.SourceCMD); i < len(d.contro.Provider().GetPriority()); i++ { + d.SourceCMD = append(d.SourceCMD, "点歌"+d.contro.Provider().GetPriority()[i]) } } @@ -102,7 +104,7 @@ func (d *Diange) Match(command string) bool { func (d *Diange) Execute(command string, args []string, danmu *liveclient.DanmuMessage) { l().Infof("%s(%s) Execute command: %s %s", danmu.User.Username, danmu.User.Uid, command, args) // if queue is full, return - if controller.UserPlaylist.Size() >= d.QueueMax { + if d.contro.Playlists().GetCurrent().Size() >= d.QueueMax { l().Info("Queue is full, ignore diange") return } @@ -130,9 +132,9 @@ func (d *Diange) Execute(command string, args []string, danmu *liveclient.DanmuM // reset cool down d.cooldowns[danmu.User.Uid] = ct if cmdType == 0 { - controller.Add(keyword, &danmu.User) + d.contro.PlayControl().Add(keyword, &danmu.User) } else { - controller.AddWithProvider(keyword, config.Provider.Priority[cmdType-1], &danmu.User) + d.contro.PlayControl().AddWithProvider(keyword, d.contro.Provider().GetPriority()[cmdType-1], &danmu.User) } } @@ -182,7 +184,7 @@ func (d *Diange) CreatePanel() fyne.CanvasObject { sourceCmds = append( sourceCmds, container.NewBorder( - nil, nil, widget.NewLabel(config.Provider.Priority[i]), nil, + nil, nil, widget.NewLabel(d.contro.Provider().GetPriority()[i]), nil, widget.NewEntryWithData(binding.BindString(&d.SourceCMD[i])))) } dgSourceCMD := container.NewBorder( diff --git a/plugin/qiege/qiege.go b/plugin/qiege/qiege.go index c214f82..16fbb70 100644 --- a/plugin/qiege/qiege.go +++ b/plugin/qiege/qiege.go @@ -1,12 +1,12 @@ package qiege import ( + "AynaLivePlayer/common/i18n" + "AynaLivePlayer/common/logger" "AynaLivePlayer/config" "AynaLivePlayer/controller" "AynaLivePlayer/gui" - "AynaLivePlayer/i18n" "AynaLivePlayer/liveclient" - "AynaLivePlayer/logger" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/data/binding" @@ -27,14 +27,16 @@ type Qiege struct { AdminPermission bool CustomCMD string panel fyne.CanvasObject + ctr controller.IController } -func NewQiege() *Qiege { +func NewQiege(ctr controller.IController) *Qiege { return &Qiege{ UserPermission: true, PrivilegePermission: true, AdminPermission: true, CustomCMD: "skip", + ctr: ctr, } } @@ -44,7 +46,7 @@ func (d *Qiege) Name() string { func (d *Qiege) Enable() error { config.LoadConfig(d) - controller.AddCommand(d) + d.ctr.LiveRooms().AddDanmuCommand(d) gui.AddConfigLayout(d) return nil } @@ -63,18 +65,18 @@ func (d *Qiege) Match(command string) bool { } func (d *Qiege) Execute(command string, args []string, danmu *liveclient.DanmuMessage) { - if d.UserPermission && (controller.CurrentMedia != nil) { - if controller.CurrentMedia.DanmuUser() != nil && controller.CurrentMedia.DanmuUser().Uid == danmu.User.Uid { - controller.PlayNext() + if d.UserPermission && (d.ctr.PlayControl().GetPlaying() != nil) { + if d.ctr.PlayControl().GetPlaying().DanmuUser() != nil && d.ctr.PlayControl().GetPlaying().DanmuUser().Uid == danmu.User.Uid { + d.ctr.PlayControl().PlayNext() return } } if d.PrivilegePermission && danmu.User.Privilege > 0 { - controller.PlayNext() + d.ctr.PlayControl().PlayNext() return } if d.AdminPermission && danmu.User.Admin { - controller.PlayNext() + d.ctr.PlayControl().PlayNext() return } } diff --git a/plugin/textinfo/textinfo.go b/plugin/textinfo/textinfo.go index bec0d45..5131af5 100644 --- a/plugin/textinfo/textinfo.go +++ b/plugin/textinfo/textinfo.go @@ -1,18 +1,17 @@ package textinfo import ( + "AynaLivePlayer/common/event" + "AynaLivePlayer/common/i18n" + "AynaLivePlayer/common/logger" "AynaLivePlayer/config" "AynaLivePlayer/controller" - "AynaLivePlayer/event" "AynaLivePlayer/gui" - "AynaLivePlayer/i18n" - "AynaLivePlayer/logger" - "AynaLivePlayer/player" + "AynaLivePlayer/model" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/widget" - "github.com/aynakeya/go-mpv" "github.com/go-resty/resty/v2" "github.com/sirupsen/logrus" "io/ioutil" @@ -42,7 +41,7 @@ type MediaInfo struct { Artist string Album string Username string - Cover player.Picture + Cover model.Picture } type OutInfo struct { @@ -61,11 +60,12 @@ type TextInfo struct { templates []*Template emptyCover []byte panel fyne.CanvasObject + ctr controller.IController } -func NewTextInfo() *TextInfo { +func NewTextInfo(ctr controller.IController) *TextInfo { b, _ := ioutil.ReadFile(config.GetAssetPath("empty.png")) - return &TextInfo{Rendering: true, emptyCover: b} + return &TextInfo{Rendering: true, emptyCover: b, ctr: ctr} } func (t *TextInfo) Title() string { @@ -201,63 +201,67 @@ func (t *TextInfo) OutputCover() { } func (t *TextInfo) registerHandlers() { - controller.MainPlayer.EventHandler.RegisterA(player.EventPlay, "plugin.textinfo.current", func(event *event.Event) { + t.ctr.PlayControl().EventManager().RegisterA(model.EventPlay, "plugin.textinfo.current", func(event *event.Event) { t.info.Current = MediaInfo{ Index: 0, - Title: event.Data.(player.PlayEvent).Media.Title, - Artist: event.Data.(player.PlayEvent).Media.Artist, - Album: event.Data.(player.PlayEvent).Media.Album, - Cover: event.Data.(player.PlayEvent).Media.Cover, - Username: event.Data.(player.PlayEvent).Media.ToUser().Name, + Title: event.Data.(model.PlayEvent).Media.Title, + Artist: event.Data.(model.PlayEvent).Media.Artist, + Album: event.Data.(model.PlayEvent).Media.Album, + Cover: event.Data.(model.PlayEvent).Media.Cover, + Username: event.Data.(model.PlayEvent).Media.ToUser().Name, } t.RenderTemplates() t.OutputCover() }) - if controller.MainPlayer.ObserveProperty("time-pos", func(property *mpv.EventProperty) { - if property.Data == nil { - t.info.CurrentTime = 0 - return - } - ct := int(property.Data.(mpv.Node).Value.(float64)) - if ct == t.info.CurrentTime { - return - } - t.info.CurrentTime = ct - t.RenderTemplates() - }) != nil { + if t.ctr.PlayControl().GetPlayer().ObserveProperty( + model.PlayerPropTimePos, "plugin.txtinfo.timepos", func(event *event.Event) { + data := event.Data.(model.PlayerPropertyUpdateEvent).Value + if data == nil { + t.info.CurrentTime = 0 + return + } + ct := int(data.(float64)) + if ct == t.info.CurrentTime { + return + } + t.info.CurrentTime = ct + t.RenderTemplates() + }) != nil { l().Error("register time-pos handler failed") } - if controller.MainPlayer.ObserveProperty("duration", func(property *mpv.EventProperty) { - if property.Data == nil { - t.info.TotalTime = 0 - return - } - t.info.TotalTime = int(property.Data.(mpv.Node).Value.(float64)) - t.RenderTemplates() - }) != nil { + if t.ctr.PlayControl().GetPlayer().ObserveProperty( + model.PlayerPropDuration, "plugin.txtinfo.duration", func(event *event.Event) { + data := event.Data.(model.PlayerPropertyUpdateEvent).Value + if data == nil { + t.info.TotalTime = 0 + return + } + t.info.TotalTime = int(data.(float64)) + t.RenderTemplates() + }) != nil { l().Error("fail to register handler for total time with property duration") } - controller.UserPlaylist.Handler.RegisterA(player.EventPlaylistUpdate, "plugin.textinfo.playlist", func(event *event.Event) { - pl := make([]MediaInfo, 0) - e := event.Data.(player.PlaylistUpdateEvent) - e.Playlist.Lock.RLock() - for index, m := range e.Playlist.Playlist { - pl = append(pl, MediaInfo{ - Index: index, - Title: m.Title, - Artist: m.Artist, - Album: m.Album, - Username: m.ToUser().Name, - }) - } - e.Playlist.Lock.RUnlock() - t.info.Playlist = pl - t.RenderTemplates() - }) - controller.CurrentLyric.Handler.RegisterA(player.EventLyricUpdate, "plugin.textinfo.lyric", func(event *event.Event) { - lrcLine := event.Data.(player.LyricUpdateEvent).Lyric - t.info.Lyric = lrcLine.Lyric - t.RenderTemplates() - }) + t.ctr.Playlists().GetCurrent().EventManager().RegisterA( + model.EventPlaylistUpdate, "plugin.textinfo.playlist", func(event *event.Event) { + pl := make([]MediaInfo, 0) + e := event.Data.(model.PlaylistUpdateEvent) + for index, m := range e.Playlist.Medias { + pl = append(pl, MediaInfo{ + Index: index, + Title: m.Title, + Artist: m.Artist, + Album: m.Album, + Username: m.ToUser().Name, + }) + } + t.info.Playlist = pl + t.RenderTemplates() + }) + t.ctr.PlayControl().GetLyric().EventManager().RegisterA( + model.EventLyricUpdate, "plugin.textinfo.lyric", func(event *event.Event) { + lrcLine := event.Data.(model.LyricUpdateEvent).Lyric + t.info.Lyric = lrcLine.Lyric + t.RenderTemplates() + }) } diff --git a/plugin/webinfo/info.go b/plugin/webinfo/info.go index 4201efa..6497ef8 100644 --- a/plugin/webinfo/info.go +++ b/plugin/webinfo/info.go @@ -1,6 +1,8 @@ package webinfo -import "AynaLivePlayer/player" +import ( + "AynaLivePlayer/model" +) type MediaInfo struct { Index int @@ -8,7 +10,7 @@ type MediaInfo struct { Artist string Album string Username string - Cover player.Picture + Cover model.Picture } type OutInfo struct { @@ -24,7 +26,7 @@ const ( OutInfoCT = "CurrentTime" OutInfoTT = "TotalTime" OutInfoL = "Lyric" - OutInfoPL = "Playlist" + OutInfoPL = "Playlists" ) type WebsocketData struct { diff --git a/plugin/webinfo/template.go b/plugin/webinfo/template.go index 12bfc6f..3878eda 100644 --- a/plugin/webinfo/template.go +++ b/plugin/webinfo/template.go @@ -1,7 +1,7 @@ package webinfo import ( - "AynaLivePlayer/util" + "AynaLivePlayer/common/util" "encoding/json" "io/ioutil" ) diff --git a/plugin/webinfo/webinfo.go b/plugin/webinfo/webinfo.go index 921edcf..5257fda 100644 --- a/plugin/webinfo/webinfo.go +++ b/plugin/webinfo/webinfo.go @@ -1,21 +1,20 @@ package webinfo import ( + "AynaLivePlayer/common/event" + "AynaLivePlayer/common/i18n" + "AynaLivePlayer/common/logger" + "AynaLivePlayer/common/util" "AynaLivePlayer/config" "AynaLivePlayer/controller" - "AynaLivePlayer/event" "AynaLivePlayer/gui" - "AynaLivePlayer/i18n" - "AynaLivePlayer/logger" - "AynaLivePlayer/player" - "AynaLivePlayer/util" + "AynaLivePlayer/model" "fmt" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" - "github.com/aynakeya/go-mpv" ) const MODULE_PLGUIN_WEBINFO = "plugin.webinfo" @@ -28,12 +27,14 @@ type WebInfo struct { Port int server *WebInfoServer panel fyne.CanvasObject + ctr controller.IController } -func NewWebInfo() *WebInfo { +func NewWebInfo(ctr controller.IController) *WebInfo { return &WebInfo{ Enabled: true, Port: 4000, + ctr: ctr, } } @@ -71,78 +72,82 @@ func (w *WebInfo) Disable() error { } func (t *WebInfo) registerHandlers() { - controller.MainPlayer.EventHandler.RegisterA(player.EventPlay, "plugin.webinfo.current", func(event *event.Event) { + t.ctr.PlayControl().EventManager().RegisterA(model.EventPlay, "plugin.webinfo.current", func(event *event.Event) { t.server.Info.Current = MediaInfo{ Index: 0, - Title: event.Data.(player.PlayEvent).Media.Title, - Artist: event.Data.(player.PlayEvent).Media.Artist, - Album: event.Data.(player.PlayEvent).Media.Album, - Cover: event.Data.(player.PlayEvent).Media.Cover, - Username: event.Data.(player.PlayEvent).Media.ToUser().Name, + Title: event.Data.(model.PlayEvent).Media.Title, + Artist: event.Data.(model.PlayEvent).Media.Artist, + Album: event.Data.(model.PlayEvent).Media.Album, + Cover: event.Data.(model.PlayEvent).Media.Cover, + Username: event.Data.(model.PlayEvent).Media.ToUser().Name, } t.server.SendInfo( OutInfoC, OutInfo{Current: t.server.Info.Current}, ) }) - if controller.MainPlayer.ObserveProperty("time-pos", func(property *mpv.EventProperty) { - if property.Data == nil { - t.server.Info.CurrentTime = 0 - return - } - ct := int(property.Data.(mpv.Node).Value.(float64)) - if ct == t.server.Info.CurrentTime { - return - } - t.server.Info.CurrentTime = ct - t.server.SendInfo( - OutInfoCT, - OutInfo{CurrentTime: t.server.Info.CurrentTime}, - ) - }) != nil { + if t.ctr.PlayControl().GetPlayer().ObserveProperty( + model.PlayerPropTimePos, "plugin.webinfo.timepos", func(event *event.Event) { + data := event.Data.(model.PlayerPropertyUpdateEvent).Value + if data == nil { + t.server.Info.CurrentTime = 0 + return + } + ct := int(data.(float64)) + if ct == t.server.Info.CurrentTime { + return + } + t.server.Info.CurrentTime = ct + t.server.SendInfo( + OutInfoCT, + OutInfo{CurrentTime: t.server.Info.CurrentTime}, + ) + }) != nil { lg.Error("register time-pos handler failed") } - if controller.MainPlayer.ObserveProperty("duration", func(property *mpv.EventProperty) { - if property.Data == nil { - t.server.Info.TotalTime = 0 - return - } - t.server.Info.TotalTime = int(property.Data.(mpv.Node).Value.(float64)) - t.server.SendInfo( - OutInfoTT, - OutInfo{TotalTime: t.server.Info.TotalTime}, - ) - }) != nil { + if t.ctr.PlayControl().GetPlayer().ObserveProperty( + model.PlayerPropDuration, "plugin.webinfo.duration", func(event *event.Event) { + data := event.Data.(model.PlayerPropertyUpdateEvent).Value + if data == nil { + t.server.Info.TotalTime = 0 + return + } + t.server.Info.TotalTime = int(data.(float64)) + t.server.SendInfo( + OutInfoTT, + OutInfo{TotalTime: t.server.Info.TotalTime}, + ) + }) != nil { lg.Error("fail to register handler for total time with property duration") } - controller.UserPlaylist.Handler.RegisterA(player.EventPlaylistUpdate, "plugin.webinfo.playlist", func(event *event.Event) { - pl := make([]MediaInfo, 0) - e := event.Data.(player.PlaylistUpdateEvent) - e.Playlist.Lock.RLock() - for index, m := range e.Playlist.Playlist { - pl = append(pl, MediaInfo{ - Index: index, - Title: m.Title, - Artist: m.Artist, - Album: m.Album, - Username: m.ToUser().Name, - }) - } - e.Playlist.Lock.RUnlock() - t.server.Info.Playlist = pl - t.server.SendInfo( - OutInfoPL, - OutInfo{Playlist: t.server.Info.Playlist}, - ) - }) - controller.CurrentLyric.Handler.RegisterA(player.EventLyricUpdate, "plugin.webinfo.lyric", func(event *event.Event) { - lrcLine := event.Data.(player.LyricUpdateEvent).Lyric - t.server.Info.Lyric = lrcLine.Lyric - t.server.SendInfo( - OutInfoL, - OutInfo{Lyric: t.server.Info.Lyric}, - ) - }) + t.ctr.Playlists().GetCurrent().EventManager().RegisterA( + model.EventPlaylistUpdate, "plugin.webinfo.playlist", func(event *event.Event) { + pl := make([]MediaInfo, 0) + e := event.Data.(model.PlaylistUpdateEvent) + for index, m := range e.Playlist.Medias { + pl = append(pl, MediaInfo{ + Index: index, + Title: m.Title, + Artist: m.Artist, + Album: m.Album, + Username: m.ToUser().Name, + }) + } + t.server.Info.Playlist = pl + t.server.SendInfo( + OutInfoPL, + OutInfo{Playlist: t.server.Info.Playlist}, + ) + }) + t.ctr.PlayControl().GetLyric().EventManager().RegisterA( + model.EventLyricUpdate, "plugin.webinfo.lyric", func(event *event.Event) { + lrcLine := event.Data.(model.LyricUpdateEvent).Lyric + t.server.Info.Lyric = lrcLine.Lyric + t.server.SendInfo( + OutInfoL, + OutInfo{Lyric: t.server.Info.Lyric}, + ) + }) } func (w *WebInfo) getServerStatusText() string { diff --git a/plugin/wylogin/wylogin.go b/plugin/wylogin/wylogin.go index e5d3da9..e3936c8 100644 --- a/plugin/wylogin/wylogin.go +++ b/plugin/wylogin/wylogin.go @@ -1,10 +1,10 @@ package wylogin import ( + "AynaLivePlayer/common/i18n" + "AynaLivePlayer/common/logger" "AynaLivePlayer/config" "AynaLivePlayer/gui" - "AynaLivePlayer/i18n" - "AynaLivePlayer/logger" "AynaLivePlayer/provider" "bytes" "fyne.io/fyne/v2" diff --git a/provider/bilibili.go b/provider/bilibili.go index 11cd289..ae45e11 100644 --- a/provider/bilibili.go +++ b/provider/bilibili.go @@ -1,7 +1,7 @@ package provider import ( - "AynaLivePlayer/player" + "AynaLivePlayer/model" "fmt" "github.com/tidwall/gjson" "net/url" @@ -37,18 +37,18 @@ func (b *Bilibili) GetName() string { return "bilibili" } -func (b *Bilibili) MatchMedia(keyword string) *player.Media { +func (b *Bilibili) MatchMedia(keyword string) *model.Media { if id := b.IdRegex0.FindString(keyword); id != "" { - return &player.Media{ - Meta: Meta{ + return &model.Media{ + Meta: model.Meta{ Name: b.GetName(), Id: id, }, } } if id := b.IdRegex1.FindString(keyword); id != "" { - return &player.Media{ - Meta: Meta{ + return &model.Media{ + Meta: model.Meta{ Name: b.GetName(), Id: id[2:], }, @@ -61,24 +61,24 @@ func (b *Bilibili) FormatPlaylistUrl(uri string) string { return "" } -func (b *Bilibili) GetPlaylist(meta Meta) ([]*player.Media, error) { +func (b *Bilibili) GetPlaylist(playlist *model.Meta) ([]*model.Media, error) { return nil, ErrorExternalApi } -func (b *Bilibili) Search(keyword string) ([]*player.Media, error) { +func (b *Bilibili) Search(keyword string) ([]*model.Media, error) { resp := httpGetString(fmt.Sprintf(b.SearchApi, url.QueryEscape(keyword)), map[string]string{ "user-agent": "BiliMusic/2.233.3", }) if resp == "" { return nil, ErrorExternalApi } - result := make([]*player.Media, 0) + result := make([]*model.Media, 0) gjson.Get(resp, "data.result").ForEach(func(key, value gjson.Result) bool { - result = append(result, &player.Media{ + result = append(result, &model.Media{ Title: value.Get("title").String(), - Cover: player.Picture{Url: value.Get("cover").String()}, + Cover: model.Picture{Url: value.Get("cover").String()}, Artist: value.Get("author").String(), - Meta: Meta{ + Meta: model.Meta{ Name: b.GetName(), Id: value.Get("id").String(), }, @@ -88,8 +88,8 @@ func (b *Bilibili) Search(keyword string) ([]*player.Media, error) { return result, nil } -func (b *Bilibili) UpdateMedia(media *player.Media) error { - resp := httpGetString(fmt.Sprintf(b.InfoApi, media.Meta.(Meta).Id), map[string]string{ +func (b *Bilibili) UpdateMedia(media *model.Media) error { + resp := httpGetString(fmt.Sprintf(b.InfoApi, media.Meta.(model.Meta).Id), map[string]string{ "user-agent": "BiliMusic/2.233.3", }) if resp == "" { @@ -105,8 +105,8 @@ func (b *Bilibili) UpdateMedia(media *player.Media) error { return nil } -func (b *Bilibili) UpdateMediaUrl(media *player.Media) error { - resp := httpGetString(fmt.Sprintf(b.FileApi, media.Meta.(Meta).Id), map[string]string{ +func (b *Bilibili) UpdateMediaUrl(media *model.Media) error { + resp := httpGetString(fmt.Sprintf(b.FileApi, media.Meta.(model.Meta).Id), map[string]string{ "user-agent": "BiliMusic/2.233.3", }) @@ -123,7 +123,7 @@ func (b *Bilibili) UpdateMediaUrl(media *player.Media) error { media.Url = uri return nil } -func (k *Bilibili) UpdateMediaLyric(media *player.Media) error { +func (k *Bilibili) UpdateMediaLyric(media *model.Media) error { return nil } diff --git a/provider/bilibili_test.go b/provider/bilibili_test.go index 49ea69f..7bce23a 100644 --- a/provider/bilibili_test.go +++ b/provider/bilibili_test.go @@ -1,7 +1,7 @@ package provider import ( - "AynaLivePlayer/player" + "AynaLivePlayer/model" "fmt" "testing" ) @@ -24,8 +24,8 @@ func TestBilibili_Search(t *testing.T) { func TestBilibili_GetMusicMeta(t *testing.T) { var api MediaProvider = BilibiliAPI - media := player.Media{ - Meta: Meta{ + media := model.Media{ + Meta: model.Meta{ Name: api.GetName(), Id: "1560601", }, @@ -40,8 +40,8 @@ func TestBilibili_GetMusicMeta(t *testing.T) { func TestBilibili_GetMusic(t *testing.T) { var api MediaProvider = BilibiliAPI - media := player.Media{ - Meta: Meta{ + media := model.Media{ + Meta: model.Meta{ Name: api.GetName(), Id: "1560601", }, diff --git a/provider/bilivideo.go b/provider/bilivideo.go index b1bfd0c..f400407 100644 --- a/provider/bilivideo.go +++ b/provider/bilivideo.go @@ -1,8 +1,8 @@ package provider import ( - "AynaLivePlayer/player" - "AynaLivePlayer/util" + "AynaLivePlayer/common/util" + "AynaLivePlayer/model" "fmt" "github.com/jinzhu/copier" "github.com/tidwall/gjson" @@ -58,10 +58,10 @@ func (b *BilibiliVideo) GetName() string { return "bilibili-video" } -func (b *BilibiliVideo) MatchMedia(keyword string) *player.Media { +func (b *BilibiliVideo) MatchMedia(keyword string) *model.Media { if id := b.IdRegex.FindString(keyword); id != "" { - return &player.Media{ - Meta: Meta{ + return &model.Media{ + Meta: model.Meta{ Name: b.GetName(), Id: id, }, @@ -70,7 +70,7 @@ func (b *BilibiliVideo) MatchMedia(keyword string) *player.Media { return nil } -func (b *BilibiliVideo) GetPlaylist(playlist Meta) ([]*player.Media, error) { +func (b *BilibiliVideo) GetPlaylist(playlist *model.Meta) ([]*model.Media, error) { return nil, ErrorExternalApi } @@ -78,7 +78,7 @@ func (b *BilibiliVideo) FormatPlaylistUrl(uri string) string { return "" } -func (b *BilibiliVideo) Search(keyword string) ([]*player.Media, error) { +func (b *BilibiliVideo) Search(keyword string) ([]*model.Media, error) { resp := httpGetString(fmt.Sprintf(b.SearchApi, url.QueryEscape(keyword)), nil) if resp == "" { return nil, ErrorExternalApi @@ -87,14 +87,14 @@ func (b *BilibiliVideo) Search(keyword string) ([]*player.Media, error) { if jresp.Get("code").String() != "0" { return nil, ErrorExternalApi } - result := make([]*player.Media, 0) + result := make([]*model.Media, 0) r := regexp.MustCompile("]*>") jresp.Get("data.result").ForEach(func(key, value gjson.Result) bool { - result = append(result, &player.Media{ + result = append(result, &model.Media{ Title: r.ReplaceAllString(value.Get("title").String(), ""), - Cover: player.Picture{Url: "https:" + value.Get("pic").String()}, + Cover: model.Picture{Url: "https:" + value.Get("pic").String()}, Artist: value.Get("author").String(), - Meta: Meta{ + Meta: model.Meta{ Name: b.GetName(), Id: value.Get("bvid").String(), }, @@ -104,8 +104,8 @@ func (b *BilibiliVideo) Search(keyword string) ([]*player.Media, error) { return result, nil } -func (b *BilibiliVideo) UpdateMedia(media *player.Media) error { - resp := httpGetString(fmt.Sprintf(b.InfoApi, b.getBv(media.Meta.(Meta).Id)), nil) +func (b *BilibiliVideo) UpdateMedia(media *model.Media) error { + resp := httpGetString(fmt.Sprintf(b.InfoApi, b.getBv(media.Meta.(model.Meta).Id)), nil) if resp == "" { return ErrorExternalApi } @@ -120,13 +120,13 @@ func (b *BilibiliVideo) UpdateMedia(media *player.Media) error { return nil } -func (b *BilibiliVideo) UpdateMediaUrl(media *player.Media) error { - resp := httpGetString(fmt.Sprintf(b.InfoApi, b.getBv(media.Meta.(Meta).Id)), nil) +func (b *BilibiliVideo) UpdateMediaUrl(media *model.Media) error { + resp := httpGetString(fmt.Sprintf(b.InfoApi, b.getBv(media.Meta.(model.Meta).Id)), nil) if resp == "" { return ErrorExternalApi } jresp := gjson.Parse(resp) - page := b.getPage(media.Meta.(Meta).Id) - 1 + page := b.getPage(media.Meta.(model.Meta).Id) - 1 cid := jresp.Get(fmt.Sprintf("data.View.pages.%d.cid", page)).String() if cid == "" { cid = jresp.Get("data.View.cid").String() @@ -134,7 +134,7 @@ func (b *BilibiliVideo) UpdateMediaUrl(media *player.Media) error { if cid == "" { return ErrorExternalApi } - resp = httpGetString(fmt.Sprintf(b.FileApi, b.getBv(media.Meta.(Meta).Id), cid), b.header) + resp = httpGetString(fmt.Sprintf(b.FileApi, b.getBv(media.Meta.(model.Meta).Id), cid), b.header) if resp == "" { return ErrorExternalApi } @@ -146,11 +146,11 @@ func (b *BilibiliVideo) UpdateMediaUrl(media *player.Media) error { media.Url = uri header := make(map[string]string) _ = copier.Copy(&header, &b.header) - header["Referer"] = fmt.Sprintf("https://www.bilibili.com/video/%s", b.getBv(media.Meta.(Meta).Id)) + header["Referer"] = fmt.Sprintf("https://www.bilibili.com/video/%s", b.getBv(media.Meta.(model.Meta).Id)) media.Header = b.header return nil } -func (b *BilibiliVideo) UpdateMediaLyric(media *player.Media) error { +func (b *BilibiliVideo) UpdateMediaLyric(media *model.Media) error { return nil } diff --git a/provider/bilivideo_test.go b/provider/bilivideo_test.go index c10957c..77f0547 100644 --- a/provider/bilivideo_test.go +++ b/provider/bilivideo_test.go @@ -1,7 +1,7 @@ package provider import ( - "AynaLivePlayer/player" + "AynaLivePlayer/model" "fmt" "regexp" "testing" @@ -10,8 +10,8 @@ import ( func TestBV_GetMusicMeta(t *testing.T) { var api MediaProvider = BilibiliVideoAPI - media := player.Media{ - Meta: Meta{ + media := model.Media{ + Meta: model.Meta{ Name: api.GetName(), Id: "BV1434y1q71P", }, @@ -26,8 +26,8 @@ func TestBV_GetMusicMeta(t *testing.T) { func TestBV_GetMusic(t *testing.T) { var api MediaProvider = BilibiliVideoAPI - media := player.Media{ - Meta: Meta{ + media := model.Media{ + Meta: model.Meta{ Name: api.GetName(), Id: "BV1434y1q71P", }, @@ -51,8 +51,8 @@ func TestBV_Regex(t *testing.T) { func TestBV_GetMusicMeta2(t *testing.T) { var api MediaProvider = BilibiliVideoAPI - media := player.Media{ - Meta: Meta{ + media := model.Media{ + Meta: model.Meta{ Name: api.GetName(), Id: "BV1gA411P7ir?p=3", }, @@ -67,8 +67,8 @@ func TestBV_GetMusicMeta2(t *testing.T) { func TestBV_GetMusic2(t *testing.T) { var api MediaProvider = BilibiliVideoAPI - media := player.Media{ - Meta: Meta{ + media := model.Media{ + Meta: model.Meta{ Name: api.GetName(), Id: "BV1gA411P7ir?p=3", }, diff --git a/provider/kuwo.go b/provider/kuwo.go index 867a967..0186a94 100644 --- a/provider/kuwo.go +++ b/provider/kuwo.go @@ -1,7 +1,7 @@ package provider import ( - "AynaLivePlayer/player" + "AynaLivePlayer/model" "fmt" "github.com/tidwall/gjson" "html" @@ -50,18 +50,18 @@ func (k *Kuwo) GetName() string { return "kuwo" } -func (k *Kuwo) MatchMedia(keyword string) *player.Media { +func (k *Kuwo) MatchMedia(keyword string) *model.Media { if id := k.IdRegex0.FindString(keyword); id != "" { - return &player.Media{ - Meta: Meta{ + return &model.Media{ + Meta: model.Meta{ Name: k.GetName(), Id: id, }, } } if id := k.IdRegex1.FindString(keyword); id != "" { - return &player.Media{ - Meta: Meta{ + return &model.Media{ + Meta: model.Meta{ Name: k.GetName(), Id: id[2:], }, @@ -107,19 +107,19 @@ func (k *Kuwo) _kuwoGet(url string) string { }) } -func (k *Kuwo) Search(keyword string) ([]*player.Media, error) { +func (k *Kuwo) Search(keyword string) ([]*model.Media, error) { resp := k._kuwoGet(fmt.Sprintf(k.SearchApi, url.QueryEscape(keyword), 1, 64)) if resp == "" { return nil, ErrorExternalApi } - result := make([]*player.Media, 0) + result := make([]*model.Media, 0) gjson.Parse(resp).Get("data.list").ForEach(func(key, value gjson.Result) bool { - result = append(result, &player.Media{ + result = append(result, &model.Media{ Title: html.UnescapeString(value.Get("name").String()), - Cover: player.Picture{Url: value.Get("pic").String()}, + Cover: model.Picture{Url: value.Get("pic").String()}, Artist: value.Get("artist").String(), Album: value.Get("album").String(), - Meta: Meta{ + Meta: model.Meta{ Name: k.GetName(), Id: value.Get("rid").String(), }, @@ -129,8 +129,8 @@ func (k *Kuwo) Search(keyword string) ([]*player.Media, error) { return result, nil } -func (k *Kuwo) UpdateMedia(media *player.Media) error { - resp := k._kuwoGet(fmt.Sprintf(k.InfoApi, media.Meta.(Meta).Id)) +func (k *Kuwo) UpdateMedia(media *model.Media) error { + resp := k._kuwoGet(fmt.Sprintf(k.InfoApi, media.Meta.(model.Meta).Id)) if resp == "" { return ErrorExternalApi } @@ -145,8 +145,8 @@ func (k *Kuwo) UpdateMedia(media *player.Media) error { return nil } -func (k *Kuwo) UpdateMediaUrl(media *player.Media) error { - result := httpGetString(fmt.Sprintf(k.FileApi, media.Meta.(Meta).Id), nil) +func (k *Kuwo) UpdateMediaUrl(media *model.Media) error { + result := httpGetString(fmt.Sprintf(k.FileApi, media.Meta.(model.Meta).Id), nil) if result == "" { return ErrorExternalApi } @@ -154,8 +154,8 @@ func (k *Kuwo) UpdateMediaUrl(media *player.Media) error { return nil } -func (k *Kuwo) UpdateMediaLyric(media *player.Media) error { - result := httpGetString(fmt.Sprintf(k.LyricApi, media.Meta.(Meta).Id), nil) +func (k *Kuwo) UpdateMediaLyric(media *model.Media) error { + result := httpGetString(fmt.Sprintf(k.LyricApi, media.Meta.(model.Meta).Id), nil) if result == "" { return ErrorExternalApi } @@ -169,12 +169,12 @@ func (k *Kuwo) UpdateMediaLyric(media *player.Media) error { return nil } -func (k *Kuwo) GetPlaylist(meta Meta) ([]*player.Media, error) { - medias := make([]*player.Media, 0) +func (k *Kuwo) GetPlaylist(playlist *model.Meta) ([]*model.Media, error) { + medias := make([]*model.Media, 0) var resp string var jresp gjson.Result for i := 1; i <= 20; i++ { - resp = k._kuwoGet(fmt.Sprintf(k.PlaylistApi, meta.Id, i, 128)) + resp = k._kuwoGet(fmt.Sprintf(k.PlaylistApi, playlist.Id, i, 128)) if resp == "" { break } @@ -190,12 +190,12 @@ func (k *Kuwo) GetPlaylist(meta Meta) ([]*player.Media, error) { jresp.Get("data.musicList").ForEach(func(key, value gjson.Result) bool { medias = append( medias, - &player.Media{ + &model.Media{ Title: html.UnescapeString(value.Get("name").String()), Artist: value.Get("artist").String(), - Cover: player.Picture{Url: value.Get("pic").String()}, + Cover: model.Picture{Url: value.Get("pic").String()}, Album: value.Get("album").String(), - Meta: Meta{ + Meta: model.Meta{ Name: k.GetName(), Id: value.Get("rid").String(), }, diff --git a/provider/kuwo_test.go b/provider/kuwo_test.go index 9eb6a84..644b371 100644 --- a/provider/kuwo_test.go +++ b/provider/kuwo_test.go @@ -1,7 +1,7 @@ package provider import ( - "AynaLivePlayer/player" + "AynaLivePlayer/model" "fmt" "testing" ) @@ -22,8 +22,8 @@ func TestKuwo_Search(t *testing.T) { func TestKuwo_GetMusicMeta(t *testing.T) { var api MediaProvider = KuwoAPI - media := player.Media{ - Meta: Meta{ + media := model.Media{ + Meta: model.Meta{ Name: api.GetName(), Id: "22804772", }, @@ -38,8 +38,8 @@ func TestKuwo_GetMusicMeta(t *testing.T) { func TestKuwo_GetMusic(t *testing.T) { var api MediaProvider = KuwoAPI - media := player.Media{ - Meta: Meta{ + media := model.Media{ + Meta: model.Meta{ Name: api.GetName(), Id: "22804772", }, @@ -58,8 +58,8 @@ func TestKuwo_GetMusic(t *testing.T) { func TestKuwo_UpdateMediaLyric(t *testing.T) { var api MediaProvider = KuwoAPI - media := player.Media{ - Meta: Meta{ + media := model.Media{ + Meta: model.Meta{ Name: api.GetName(), Id: "22804772", }, @@ -71,7 +71,7 @@ func TestKuwo_UpdateMediaLyric(t *testing.T) { func TestKuwo_GetPlaylist(t *testing.T) { var api MediaProvider = KuwoAPI - playlist, err := api.GetPlaylist(Meta{ + playlist, err := api.GetPlaylist(model.Meta{ Name: api.GetName(), //Id: "1082685104", Id: "2959147566", diff --git a/provider/local.go b/provider/local.go index 9d72982..eef4f5e 100644 --- a/provider/local.go +++ b/provider/local.go @@ -1,8 +1,7 @@ package provider import ( - "AynaLivePlayer/config" - "AynaLivePlayer/player" + "AynaLivePlayer/model" "os" "sort" "strings" @@ -10,31 +9,29 @@ import ( type _LocalPlaylist struct { Name string - Medias []*player.Media + Medias []*model.Media } type Local struct { + localDir string Playlists []*_LocalPlaylist } var LocalAPI *Local -func init() { - LocalAPI = _newLocal() - Providers[LocalAPI.GetName()] = LocalAPI -} - -func _newLocal() *Local { - l := &Local{Playlists: make([]*_LocalPlaylist, 0)} - if err := os.MkdirAll(config.Provider.LocalDir, 0755); err != nil { +func NewLocal(localdir string) *Local { + l := &Local{Playlists: make([]*_LocalPlaylist, 0), localDir: localdir} + if err := os.MkdirAll(localdir, 0755); err != nil { return l } - for _, n := range getPlaylistNames() { + for _, n := range getPlaylistNames(localdir) { l.Playlists = append(l.Playlists, &_LocalPlaylist{Name: n}) } for i, _ := range l.Playlists { - _ = readLocalPlaylist(l.Playlists[i]) + _ = readLocalPlaylist(localdir, l.Playlists[i]) } + LocalAPI = l + Providers[LocalAPI.GetName()] = LocalAPI return l } @@ -42,11 +39,11 @@ func (l *Local) GetName() string { return "local" } -func (l *Local) MatchMedia(keyword string) *player.Media { +func (l *Local) MatchMedia(keyword string) *model.Media { return nil } -func (l *Local) UpdateMediaLyric(media *player.Media) error { +func (l *Local) UpdateMediaLyric(media *model.Media) error { // already update in UpdateMedia, do nothing return nil } @@ -55,7 +52,7 @@ func (l *Local) FormatPlaylistUrl(uri string) string { return uri } -func (l *Local) GetPlaylist(playlist Meta) ([]*player.Media, error) { +func (l *Local) GetPlaylist(playlist *model.Meta) ([]*model.Media, error) { var pl *_LocalPlaylist = nil for _, p := range l.Playlists { if p.Name == playlist.Id { @@ -66,15 +63,15 @@ func (l *Local) GetPlaylist(playlist Meta) ([]*player.Media, error) { l.Playlists = append(l.Playlists, &_LocalPlaylist{Name: playlist.Id}) pl = l.Playlists[len(l.Playlists)-1] } - if readLocalPlaylist(pl) != nil { + if readLocalPlaylist(l.localDir, pl) != nil { return nil, ErrorExternalApi } return pl.Medias, nil } -func (l *Local) Search(keyword string) ([]*player.Media, error) { +func (l *Local) Search(keyword string) ([]*model.Media, error) { result := make([]struct { - M *player.Media + M *model.Media N int }, 0) keywords := strings.Split(keyword, " ") @@ -94,7 +91,7 @@ func (l *Local) Search(keyword string) ([]*player.Media, error) { } if n > 0 { result = append(result, struct { - M *player.Media + M *model.Media N int }{M: m, N: n}) } @@ -103,15 +100,15 @@ func (l *Local) Search(keyword string) ([]*player.Media, error) { sort.Slice(result, func(i, j int) bool { return result[i].N > result[j].N }) - medias := make([]*player.Media, len(result)) + medias := make([]*model.Media, len(result)) for i, r := range result { medias[i] = r.M.Copy() } return medias, nil } -func (l *Local) UpdateMedia(media *player.Media) error { - mediaPath := media.Meta.(Meta).Id +func (l *Local) UpdateMedia(media *model.Media) error { + mediaPath := media.Meta.(model.Meta).Id _, err := os.Stat(mediaPath) if err != nil { return err @@ -119,8 +116,8 @@ func (l *Local) UpdateMedia(media *player.Media) error { return readMediaFile(media) } -func (l *Local) UpdateMediaUrl(media *player.Media) error { - mediaPath := media.Meta.(Meta).Id +func (l *Local) UpdateMediaUrl(media *model.Media) error { + mediaPath := media.Meta.(model.Meta).Id _, err := os.Stat(mediaPath) if err != nil { return err diff --git a/provider/local_helper.go b/provider/local_helper.go index bafb9ac..862eda4 100644 --- a/provider/local_helper.go +++ b/provider/local_helper.go @@ -1,18 +1,17 @@ package provider import ( - "AynaLivePlayer/config" - "AynaLivePlayer/player" - "AynaLivePlayer/util" + "AynaLivePlayer/common/util" + "AynaLivePlayer/model" "github.com/dhowden/tag" "io/ioutil" "os" "path/filepath" ) -func getPlaylistNames() []string { +func getPlaylistNames(localdir string) []string { names := make([]string, 0) - items, _ := ioutil.ReadDir(config.Provider.LocalDir) + items, _ := ioutil.ReadDir(localdir) for _, item := range items { if item.IsDir() { names = append(names, item.Name()) @@ -24,10 +23,10 @@ func getPlaylistNames() []string { // readLocalPlaylist read files under a directory // and return a _LocalPlaylist object. // This function assume this directory exists -func readLocalPlaylist(playlist *_LocalPlaylist) error { +func readLocalPlaylist(localdir string, playlist *_LocalPlaylist) error { p1th := playlist.Name - playlist.Medias = make([]*player.Media, 0) - fullPath := filepath.Join(config.Provider.LocalDir, p1th) + playlist.Medias = make([]*model.Media, 0) + fullPath := filepath.Join(localdir, p1th) if _, err := os.Stat(fullPath); os.IsNotExist(err) { return err } @@ -36,8 +35,8 @@ func readLocalPlaylist(playlist *_LocalPlaylist) error { // if item is a file, read file if !item.IsDir() { fn := item.Name() - media := player.Media{ - Meta: Meta{ + media := model.Media{ + Meta: model.Meta{ Name: LocalAPI.GetName(), Id: filepath.Join(fullPath, fn), }, @@ -51,8 +50,8 @@ func readLocalPlaylist(playlist *_LocalPlaylist) error { return nil } -func readMediaFile(media *player.Media) error { - p := media.Meta.(Meta).Id +func readMediaFile(media *model.Media) error { + p := media.Meta.(model.Meta).Id f, err := os.Open(p) if err != nil { return err diff --git a/provider/netease.go b/provider/netease.go index 142f093..c385ab2 100644 --- a/provider/netease.go +++ b/provider/netease.go @@ -1,8 +1,8 @@ package provider import ( - "AynaLivePlayer/player" - "AynaLivePlayer/util" + "AynaLivePlayer/common/util" + "AynaLivePlayer/model" neteaseApi "github.com/XiaoMengXinX/Music163Api-Go/api" neteaseTypes "github.com/XiaoMengXinX/Music163Api-Go/types" neteaseUtil "github.com/XiaoMengXinX/Music163Api-Go/utils" @@ -61,18 +61,18 @@ func (n *Netease) GetName() string { return "netease" } -func (n *Netease) MatchMedia(keyword string) *player.Media { +func (n *Netease) MatchMedia(keyword string) *model.Media { if id := n.IdRegex0.FindString(keyword); id != "" { - return &player.Media{ - Meta: Meta{ + return &model.Media{ + Meta: model.Meta{ Name: n.GetName(), Id: id, }, } } if id := n.IdRegex1.FindString(keyword); id != "" { - return &player.Media{ - Meta: Meta{ + return &model.Media{ + Meta: model.Meta{ Name: n.GetName(), Id: id[2:], }, @@ -94,9 +94,9 @@ func (n *Netease) FormatPlaylistUrl(uri string) string { return "" } -func (n *Netease) GetPlaylist(meta Meta) ([]*player.Media, error) { +func (n *Netease) GetPlaylist(playlist *model.Meta) ([]*model.Media, error) { result, err := neteaseApi.GetPlaylistDetail( - n.ReqData, util.StringToInt(meta.Id)) + n.ReqData, util.StringToInt(playlist.Id)) if err != nil || result.Code != 200 { return nil, ErrorExternalApi } @@ -109,7 +109,7 @@ func (n *Netease) GetPlaylist(meta Meta) ([]*player.Media, error) { for i := 0; i < cnt; i++ { ids[i] = result.Playlist.TrackIds[i].Id } - medias := make([]*player.Media, 0, cnt) + medias := make([]*model.Media, 0, cnt) for index := 0; index < len(ids); index += 1000 { result2, err := neteaseApi.GetSongDetail( n.ReqData, @@ -122,15 +122,15 @@ func (n *Netease) GetPlaylist(meta Meta) ([]*player.Media, error) { break } for i := 0; i < cnt; i++ { - medias = append(medias, &player.Media{ + medias = append(medias, &model.Media{ Title: result2.Songs[i].Name, Artist: _neteaseGetArtistNames(result2.Songs[i]), - Cover: player.Picture{Url: result2.Songs[i].Al.PicUrl}, + Cover: model.Picture{Url: result2.Songs[i].Al.PicUrl}, Album: result2.Songs[i].Al.Name, Url: "", Header: nil, User: nil, - Meta: Meta{ + Meta: model.Meta{ Name: n.GetName(), Id: strconv.Itoa(result2.Songs[i].Id), }, @@ -143,7 +143,7 @@ func (n *Netease) GetPlaylist(meta Meta) ([]*player.Media, error) { return medias, nil } -func (n *Netease) Search(keyword string) ([]*player.Media, error) { +func (n *Netease) Search(keyword string) ([]*model.Media, error) { rawResult, err := neteaseApi.SearchSong( n.ReqData, neteaseApi.SearchSongConfig{ @@ -154,20 +154,20 @@ func (n *Netease) Search(keyword string) ([]*player.Media, error) { if err != nil || rawResult.Code != 200 { return nil, ErrorExternalApi } - medias := make([]*player.Media, 0) + medias := make([]*model.Media, 0) for _, song := range rawResult.Result.Songs { artists := make([]string, 0) for _, a := range song.Artists { artists = append(artists, a.Name) } - medias = append(medias, &player.Media{ + medias = append(medias, &model.Media{ Title: song.Name, Artist: strings.Join(artists, ","), - Cover: player.Picture{}, + Cover: model.Picture{}, Album: song.Album.Name, Url: "", Header: nil, - Meta: Meta{ + Meta: model.Meta{ Name: n.GetName(), Id: strconv.Itoa(song.Id), }, @@ -176,10 +176,10 @@ func (n *Netease) Search(keyword string) ([]*player.Media, error) { return medias, nil } -func (n *Netease) UpdateMedia(media *player.Media) error { +func (n *Netease) UpdateMedia(media *model.Media) error { result, err := neteaseApi.GetSongDetail( n.ReqData, - []int{util.StringToInt(media.Meta.(Meta).Id)}) + []int{util.StringToInt(media.Meta.(model.Meta).Id)}) if err != nil || result.Code != 200 { return ErrorExternalApi } @@ -193,10 +193,10 @@ func (n *Netease) UpdateMedia(media *player.Media) error { return nil } -func (n *Netease) UpdateMediaUrl(media *player.Media) error { +func (n *Netease) UpdateMediaUrl(media *model.Media) error { result, err := neteaseApi.GetSongURL( n.ReqData, - neteaseApi.SongURLConfig{Ids: []int{util.StringToInt(media.Meta.(Meta).Id)}}) + neteaseApi.SongURLConfig{Ids: []int{util.StringToInt(media.Meta.(model.Meta).Id)}}) if err != nil || result.Code != 200 { return ErrorExternalApi } @@ -210,8 +210,8 @@ func (n *Netease) UpdateMediaUrl(media *player.Media) error { return nil } -func (n *Netease) UpdateMediaLyric(media *player.Media) error { - result, err := neteaseApi.GetSongLyric(n.ReqData, util.StringToInt(media.Meta.(Meta).Id)) +func (n *Netease) UpdateMediaLyric(media *model.Media) error { + result, err := neteaseApi.GetSongLyric(n.ReqData, util.StringToInt(media.Meta.(model.Meta).Id)) if err != nil || result.Code != 200 { return ErrorExternalApi } diff --git a/provider/netease_test.go b/provider/netease_test.go index ebb74e7..2297a47 100644 --- a/provider/netease_test.go +++ b/provider/netease_test.go @@ -1,7 +1,7 @@ package provider import ( - "AynaLivePlayer/player" + "AynaLivePlayer/model" "fmt" "testing" ) @@ -23,8 +23,8 @@ func TestNetease_Search(t *testing.T) { func TestNetease_GetMusicMeta(t *testing.T) { var api MediaProvider = NeteaseAPI - media := player.Media{ - Meta: Meta{ + media := model.Media{ + Meta: model.Meta{ Name: api.GetName(), Id: "33516503", }, @@ -39,8 +39,8 @@ func TestNetease_GetMusicMeta(t *testing.T) { func TestNetease_GetMusic(t *testing.T) { var api MediaProvider = NeteaseAPI - media := player.Media{ - Meta: Meta{ + media := model.Media{ + Meta: model.Meta{ Name: api.GetName(), Id: "33516503", }, @@ -59,7 +59,7 @@ func TestNetease_GetMusic(t *testing.T) { func TestNetease_GetPlaylist(t *testing.T) { var api MediaProvider = NeteaseAPI - playlist, err := api.GetPlaylist(Meta{ + playlist, err := api.GetPlaylist(model.Meta{ Name: api.GetName(), //Id: "2520739691", Id: "2382819181", @@ -77,8 +77,8 @@ func TestNetease_GetPlaylist(t *testing.T) { func TestNetease_UpdateMediaLyric(t *testing.T) { var api MediaProvider = NeteaseAPI - media := player.Media{ - Meta: Meta{ + media := model.Media{ + Meta: model.Meta{ Name: api.GetName(), Id: "33516503", }, diff --git a/provider/provider.go b/provider/provider.go index 5d1dfbd..f17a465 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -1,8 +1,8 @@ package provider import ( - "AynaLivePlayer/logger" - "AynaLivePlayer/player" + "AynaLivePlayer/common/logger" + "AynaLivePlayer/model" "github.com/sirupsen/logrus" ) @@ -12,25 +12,20 @@ func l() *logrus.Entry { return logger.Logger.WithField("Module", MODULE_CONTROLLER) } -type Meta struct { - Name string - Id string -} - type MediaProvider interface { GetName() string - MatchMedia(keyword string) *player.Media - GetPlaylist(playlist Meta) ([]*player.Media, error) + MatchMedia(keyword string) *model.Media + GetPlaylist(playlist *model.Meta) ([]*model.Media, error) FormatPlaylistUrl(uri string) string - Search(keyword string) ([]*player.Media, error) - UpdateMedia(media *player.Media) error - UpdateMediaUrl(media *player.Media) error - UpdateMediaLyric(media *player.Media) error + Search(keyword string) ([]*model.Media, error) + UpdateMedia(media *model.Media) error + UpdateMediaUrl(media *model.Media) error + UpdateMediaLyric(media *model.Media) error } var Providers map[string]MediaProvider = make(map[string]MediaProvider) -func GetPlaylist(meta Meta) ([]*player.Media, error) { +func GetPlaylist(meta *model.Meta) ([]*model.Media, error) { if v, ok := Providers[meta.Name]; ok { return v.GetPlaylist(meta) } @@ -44,36 +39,36 @@ func FormatPlaylistUrl(pname, uri string) (string, error) { return "", ErrorNoSuchProvider } -func MatchMedia(provider string, keyword string) *player.Media { +func MatchMedia(provider string, keyword string) *model.Media { if v, ok := Providers[provider]; ok { return v.MatchMedia(keyword) } return nil } -func Search(provider string, keyword string) ([]*player.Media, error) { +func Search(provider string, keyword string) ([]*model.Media, error) { if v, ok := Providers[provider]; ok { return v.Search(keyword) } return nil, ErrorNoSuchProvider } -func UpdateMedia(media *player.Media) error { - if v, ok := Providers[media.Meta.(Meta).Name]; ok { +func UpdateMedia(media *model.Media) error { + if v, ok := Providers[media.Meta.(model.Meta).Name]; ok { return v.UpdateMedia(media) } return ErrorNoSuchProvider } -func UpdateMediaUrl(media *player.Media) error { - if v, ok := Providers[media.Meta.(Meta).Name]; ok { +func UpdateMediaUrl(media *model.Media) error { + if v, ok := Providers[media.Meta.(model.Meta).Name]; ok { return v.UpdateMediaUrl(media) } return ErrorNoSuchProvider } -func UpdateMediaLyric(media *player.Media) error { - if v, ok := Providers[media.Meta.(Meta).Name]; ok { +func UpdateMediaLyric(media *model.Media) error { + if v, ok := Providers[media.Meta.(model.Meta).Name]; ok { return v.UpdateMediaLyric(media) } return ErrorNoSuchProvider