diff --git a/assets/translation.json b/assets/translation.json index 70e176c..828cd7e 100644 --- a/assets/translation.json +++ b/assets/translation.json @@ -4,6 +4,30 @@ "zh-CN" ], "Messages": { + "gui.config.basic.audio_device": { + "en": "Audio Device", + "zh-CN": "音频输出设备" + }, + "gui.config.basic.description": { + "en": "Basic Configuration", + "zh-CN": "基础设置" + }, + "gui.config.basic.random_playlist": { + "en": "Playlist Random", + "zh-CN": "播放列表随机设置(打勾表示随机播放)" + }, + "gui.config.basic.random_playlist.system": { + "en": "System Playlist", + "zh-CN": "闲置歌单" + }, + "gui.config.basic.random_playlist.user": { + "en": "User Playlist", + "zh-CN": "用户歌单" + }, + "gui.config.basic.title": { + "en": "Basic", + "zh-CN": "基础设置" + }, "gui.player.button.lrc": { "en": "lrc", "zh-CN": "歌词" diff --git a/config/config.go b/config/config.go index 602625f..8f5b748 100644 --- a/config/config.go +++ b/config/config.go @@ -6,7 +6,7 @@ import ( "path" ) -const VERSION = "alpha 0.6.5" +const VERSION = "alpha 0.6.7" const CONFIG_PATH = "./config.ini" const Assests_PATH = "./assets" diff --git a/config/config_player.go b/config/config_player.go index b57ec52..7537e04 100644 --- a/config/config_player.go +++ b/config/config_player.go @@ -5,6 +5,8 @@ type _PlayerConfig struct { PlaylistsProvider []string PlaylistIndex int PlaylistRandom bool + AudioDevice string + Volume float64 } func (c *_PlayerConfig) Name() string { @@ -16,4 +18,6 @@ var Player = &_PlayerConfig{ PlaylistsProvider: []string{"netease", "netease", "netease"}, PlaylistIndex: 0, PlaylistRandom: true, + AudioDevice: "auto", + Volume: 100, } diff --git a/controller/controller.go b/controller/controller.go index c812b0c..61628af 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -19,99 +19,6 @@ func l() *logrus.Entry { return logger.Logger.WithField("Module", MODULE_CONTROLLER) } -func PlayNext() { - l().Info("try to play next possible media") - if UserPlaylist.Size() == 0 && SystemPlaylist.Size() == 0 { - return - } - var media *player.Media - if UserPlaylist.Size() != 0 { - media = UserPlaylist.Pop() - } else if SystemPlaylist.Size() != 0 { - media = SystemPlaylist.Next() - } - Play(media) -} - -func Play(media *player.Media) { - l().Info("prepare media") - err := PrepareMedia(media) - if err != nil { - l().Warn("prepare media failed. try play next") - PlayNext() - return - } - CurrentMedia = media - if err := MainPlayer.Play(media); err != nil { - l().Warn("play failed", err) - } - CurrentLyric.Reload(media.Lyric) - // reset - media.Url = "" -} - -func Add(keyword string, user interface{}) { - medias, err := Search(keyword) - if err != nil { - l().Warnf("search for %s, got error %s", keyword, err) - return - } - if len(medias) == 0 { - l().Info("search for %s, got no result", keyword) - return - } - media := medias[0] - media.User = user - l().Infof("add media %s (%s)", media.Title, media.Artist) - UserPlaylist.Insert(-1, media) -} - -func AddWithProvider(keyword string, pname string, user interface{}) { - medias, err := provider.Search(pname, keyword) - if err != nil { - l().Warnf("search for %s, got error %s", keyword, err) - return - } - if len(medias) == 0 { - l().Info("search for %s, got no result", keyword) - } - media := medias[0] - media.User = user - l().Info("add media %s (%s)", media.Title, media.Artist) - UserPlaylist.Insert(-1, media) -} - -func Seek(position float64, absolute bool) { - if err := MainPlayer.Seek(position, absolute); err != nil { - l().Warnf("seek to position %f (%t) failed, %s", position, absolute, err) - } -} - -func Toggle() (b bool) { - var err error - if MainPlayer.IsPaused() { - err = MainPlayer.Unpause() - b = false - } else { - err = MainPlayer.Pause() - b = true - } - if err != nil { - l().Warn("toggle failed", err) - } - return -} - -func SetVolume(volume float64) { - if MainPlayer.SetVolume(volume) != nil { - l().Warnf("set mpv volume to %f failed", volume) - } -} - -func Destroy() { - MainPlayer.Stop() -} - func SetDanmuClient(roomId string) { ResetDanmuClient() l().Infof("setting live client for %s", roomId) diff --git a/controller/global.go b/controller/global.go index 4084e83..aa3d581 100644 --- a/controller/global.go +++ b/controller/global.go @@ -19,6 +19,8 @@ var CurrentMedia *player.Media func Initialize() { MainPlayer = player.NewPlayer() + SetAudioDevice(config.Player.AudioDevice) + SetVolume(config.Player.Volume) UserPlaylist = player.NewPlaylist("user", player.PlaylistConfig{RandomNext: false}) SystemPlaylist = player.NewPlaylist("system", player.PlaylistConfig{RandomNext: config.Player.PlaylistRandom}) PlaylistManager = make([]*player.Playlist, 0) @@ -29,6 +31,7 @@ func Initialize() { UserPlaylist.Handler.RegisterA(player.EventPlaylistInsert, "controller.playnextwhenadd", handlePlaylistAdd) MainPlayer.ObserveProperty("time-pos", handleLyricUpdate) MainPlayer.Start() + } func loadPlaylists() { diff --git a/controller/player.go b/controller/player.go new file mode 100644 index 0000000..82b6941 --- /dev/null +++ b/controller/player.go @@ -0,0 +1,121 @@ +package controller + +import ( + "AynaLivePlayer/config" + "AynaLivePlayer/player" + "AynaLivePlayer/provider" +) + +func PlayNext() { + l().Info("try to play next possible media") + if UserPlaylist.Size() == 0 && SystemPlaylist.Size() == 0 { + return + } + var media *player.Media + if UserPlaylist.Size() != 0 { + media = UserPlaylist.Pop() + } else if SystemPlaylist.Size() != 0 { + media = SystemPlaylist.Next() + } + Play(media) +} + +func Play(media *player.Media) { + l().Info("prepare media") + err := PrepareMedia(media) + if err != nil { + l().Warn("prepare media failed. try play next") + PlayNext() + return + } + CurrentMedia = media + if err := MainPlayer.Play(media); err != nil { + l().Warn("play failed", err) + } + CurrentLyric.Reload(media.Lyric) + // reset + media.Url = "" +} + +func Add(keyword string, user interface{}) { + medias, err := Search(keyword) + if err != nil { + l().Warnf("search for %s, got error %s", keyword, err) + return + } + if len(medias) == 0 { + l().Info("search for %s, got no result", keyword) + return + } + media := medias[0] + media.User = user + l().Infof("add media %s (%s)", media.Title, media.Artist) + UserPlaylist.Insert(-1, media) +} + +func AddWithProvider(keyword string, pname string, user interface{}) { + medias, err := provider.Search(pname, keyword) + if err != nil { + l().Warnf("search for %s, got error %s", keyword, err) + return + } + if len(medias) == 0 { + l().Info("search for %s, got no result", keyword) + } + media := medias[0] + media.User = user + l().Info("add media %s (%s)", media.Title, media.Artist) + UserPlaylist.Insert(-1, media) +} + +func Seek(position float64, absolute bool) { + if err := MainPlayer.Seek(position, absolute); err != nil { + l().Warnf("seek to position %f (%t) failed, %s", position, absolute, err) + } +} + +func Toggle() (b bool) { + var err error + if MainPlayer.IsPaused() { + err = MainPlayer.Unpause() + b = false + } else { + err = MainPlayer.Pause() + b = true + } + if err != nil { + l().Warn("toggle failed", err) + } + return +} + +func SetVolume(volume float64) { + if MainPlayer.SetVolume(volume) != nil { + l().Warnf("set mpv volume to %f failed", volume) + return + } + config.Player.Volume = volume +} + +func Destroy() { + MainPlayer.Stop() +} + +func GetAudioDevices() []player.AudioDevice { + dl, err := MainPlayer.GetAudioDeviceList() + if err != nil { + return make([]player.AudioDevice, 0) + } + return dl +} + +func SetAudioDevice(device string) { + l().Infof("set audio device to %s", device) + if err := MainPlayer.SetAudioDevice(device); err != nil { + l().Warnf("set mpv audio device to %s failed, %s", device, err) + MainPlayer.SetAudioDevice("auto") + config.Player.AudioDevice = "auto" + return + } + config.Player.AudioDevice = device +} diff --git a/gui/config_basic.go b/gui/config_basic.go index 09bc158..30f20f3 100644 --- a/gui/config_basic.go +++ b/gui/config_basic.go @@ -1,20 +1,54 @@ package gui import ( + "AynaLivePlayer/config" + "AynaLivePlayer/controller" + "AynaLivePlayer/i18n" "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/data/binding" + "fyne.io/fyne/v2/widget" ) -type bascicConfig struct{} - -func (b bascicConfig) Title() string { - return "Basic" +type bascicConfig struct { + panel fyne.CanvasObject } -func (b bascicConfig) Description() string { - return "Basic configuration" +func (b *bascicConfig) Title() string { + return i18n.T("gui.config.basic.title") } -func (b bascicConfig) Create() fyne.CanvasObject { - //TODO implement me - panic("implement me") +func (b *bascicConfig) Description() string { + return i18n.T("gui.config.basic.description") +} + +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( + i18n.T("gui.config.basic.random_playlist.user"), + binding.BindBool(&controller.UserPlaylist.Config.RandomNext)), + widget.NewCheckWithData( + i18n.T("gui.config.basic.random_playlist.system"), + binding.BindBool(&controller.SystemPlaylist.Config.RandomNext)), + ) + devices := controller.GetAudioDevices() + deviceDesc := make([]string, len(devices)) + deviceDesc2Name := make(map[string]string) + for i, device := range devices { + deviceDesc[i] = device.Description + deviceDesc2Name[device.Description] = device.Name + } + deviceSel := widget.NewSelect(deviceDesc, func(s string) { + controller.SetAudioDevice(deviceDesc2Name[s]) + }) + deviceSel.Selected = config.Player.AudioDevice + outputDevice := container.NewBorder(nil, nil, + widget.NewLabel(i18n.T("gui.config.basic.audio_device")), nil, + deviceSel) + b.panel = container.NewVBox(randomPlaylist, outputDevice) + return b.panel } diff --git a/gui/gui.go b/gui/gui.go index c23dda1..3855390 100644 --- a/gui/gui.go +++ b/gui/gui.go @@ -22,7 +22,7 @@ type ConfigLayout interface { var App fyne.App var MainWindow fyne.Window -var ConfigList = []ConfigLayout{} +var ConfigList = []ConfigLayout{&bascicConfig{}} func l() *logrus.Entry { return logger.Logger.WithField("Module", MODULE_GUI) diff --git a/gui/player_controller.go b/gui/player_controller.go index 3472caa..01c6498 100644 --- a/gui/player_controller.go +++ b/gui/player_controller.go @@ -141,6 +141,7 @@ func registerPlayControllerHandler() { 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) } diff --git a/gui/player_playlist.go b/gui/player_playlist.go index d0cdac4..9cdf4b0 100644 --- a/gui/player_playlist.go +++ b/gui/player_playlist.go @@ -25,7 +25,7 @@ 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() { - fmt.Println("delete", b.Index) + controller.UserPlaylist.Delete(b.Index) }) topItem := fyne.NewMenuItem(i18n.T("gui.player.playlist.op.top"), func() { controller.UserPlaylist.Move(b.Index, 0) diff --git a/player/player.go b/player/player.go index 991d4e4..3f83db6 100644 --- a/player/player.go +++ b/player/player.go @@ -6,6 +6,7 @@ import ( "AynaLivePlayer/util" "github.com/aynakeya/go-mpv" "github.com/sirupsen/logrus" + "github.com/tidwall/gjson" ) const MODULE_PLAYER = "Player.Player" @@ -154,3 +155,32 @@ func (p *Player) ObserveProperty(property string, handler ...PropertyHandlerFunc } 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) +} diff --git a/player/playlist.go b/player/playlist.go index 7fb0cbf..0768c43 100644 --- a/player/playlist.go +++ b/player/playlist.go @@ -58,7 +58,14 @@ func (p *Playlist) Pop() *Media { return nil } p.Lock.Lock() - media := p.Playlist[0] + 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}) diff --git a/todo.txt b/todo.txt index 5fe9f82..ebe715b 100644 --- a/todo.txt +++ b/todo.txt @@ -5,15 +5,17 @@ - @5 delete optimization - 歌词来源 - - 文本输出 - web输出 +- 历史记录 +- 黑名单 - 进入beta版本 ---- Finished +- 2022.6.26: i18n - 2022.6.25: kuwo歌单 - 2022.6.25: 设置界面 - 2022.6.25: @6 bug, race condition, playlist size changed during playlist update.