config panel, kuwo source, playlist operation, bug fix @6, panic handling

This commit is contained in:
Aynakeya
2022-06-25 14:08:50 -07:00
parent 9f75839ebc
commit 0a53e8220e
40 changed files with 920 additions and 3992 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.idea

View File

@@ -5,14 +5,30 @@ import (
"AynaLivePlayer/controller"
"AynaLivePlayer/gui"
"AynaLivePlayer/logger"
"AynaLivePlayer/plugin/diange"
"AynaLivePlayer/plugin/qiege"
"fmt"
"github.com/mitchellh/panicwrap"
"github.com/sirupsen/logrus"
"os"
)
func init() {
exitStatus, _ := panicwrap.BasicWrap(func(s string) {
logger.Logger.Panic(s)
os.Exit(1)
return
})
if exitStatus >= 0 {
os.Exit(exitStatus)
}
}
func main() {
fmt.Printf("BiliAudioBot Revive %s\n", config.VERSION)
logger.Logger.SetLevel(logrus.DebugLevel)
controller.Initialize()
controller.LoadPlugins(diange.NewDiange(), qiege.NewQiege())
defer func() {
controller.Destroy()
config.SaveToConfigFile(config.CONFIG_PATH)

View File

@@ -1,23 +1,18 @@
package main
import (
"image/color"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
)
import "fmt"
func main() {
myApp := app.New()
myWindow := myApp.NewWindow("Form Layout")
label1 := canvas.NewText("Label 1", color.Black)
value1 := canvas.NewText("Value", color.Black)
label2 := canvas.NewText("Label 2", color.Black)
value2 := canvas.NewText("Something", color.Black)
grid := container.New(layout.NewFormLayout(), label1, value1, label2, value2)
myWindow.SetContent(grid)
myWindow.ShowAndRun()
defer func(x int) { fmt.Println(x) }(func() int { fmt.Println("build"); return 123 }())
fmt.Println("main")
//myApp := app.New()
//myWindow := myApp.NewWindow("Form Layout")
//
//label1 := canvas.NewText("Label 1", color.Black)
//value1 := canvas.NewText("Value", color.Black)
//label2 := canvas.NewText("Label 2", color.Black)
//value2 := canvas.NewText("Something", color.Black)
//grid := container.New(layout.NewFormLayout(), label1, value1, label2, value2)
//myWindow.SetContent(grid)
//myWindow.ShowAndRun()
}

View File

@@ -1,17 +0,0 @@
[Log]
Path = ./log.txt
Level = 4
[LiveRoom]
History = 9076804,3819533
[Player]
Playlists = 116746576,467824586
PlaylistsProvider = netease,netease
PlaylistIndex = 0
PlaylistRandom = true
[Provider]
Priority = local,netease,kuwo,bilibili
LocalDir = ./music

View File

@@ -2,12 +2,11 @@ package config
import (
"fmt"
"github.com/sirupsen/logrus"
"gopkg.in/ini.v1"
"path"
)
const VERSION = "alpha 0.4"
const VERSION = "alpha 0.6"
const CONFIG_PATH = "./config.ini"
const Assests_PATH = "./assets"
@@ -16,91 +15,40 @@ func GetAssetPath(name string) string {
return path.Join(Assests_PATH, name)
}
type LogConfig struct {
Path string
Level logrus.Level
type Config interface {
Name() string
}
var Log = &LogConfig{
Path: "./log.txt",
Level: logrus.InfoLevel,
}
var ConfigFile *ini.File
var Configs = make([]Config, 0)
type LiveRoomConfig struct {
History []string
}
var LiveRoom = &LiveRoomConfig{History: []string{"9076804", "3819533"}}
type PlayerConfig struct {
Playlists []string
PlaylistsProvider []string
PlaylistIndex int
PlaylistRandom bool
}
var Player = &PlayerConfig{
Playlists: []string{"116746576", "646548465"},
PlaylistsProvider: []string{"netease", "netease"},
PlaylistIndex: 0,
PlaylistRandom: true,
}
type ProviderConfig struct {
Priority []string
LocalDir string
}
var Provider = &ProviderConfig{
Priority: []string{"local", "netease", "kuwo", "bilibili"},
LocalDir: "./music",
func LoadConfig(cfg Config) {
sec, err := ConfigFile.GetSection(cfg.Name())
if err == nil {
_ = sec.MapTo(cfg)
}
Configs = append(Configs, cfg)
return
}
func init() {
cfg, err := ini.Load(CONFIG_PATH)
var err error
ConfigFile, err = ini.Load(CONFIG_PATH)
if err != nil {
fmt.Println("config not found")
SaveToConfigFile(CONFIG_PATH)
return
fmt.Println("config not found, using default config")
ConfigFile = ini.Empty()
}
err = cfg.Section("Log").MapTo(Log)
if err != nil {
fmt.Println(err)
return
}
err = cfg.Section("LiveRoom").MapTo(LiveRoom)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(cfg.Section("Player").GetKey("Playlists"))
err = cfg.Section("Player").MapTo(Player)
if err != nil {
fmt.Println(err)
return
}
err = cfg.Section("Provider").MapTo(Provider)
if err != nil {
fmt.Println(err)
return
for _, cfg := range []Config{Log, LiveRoom, Player, Provider} {
LoadConfig(cfg)
}
}
func SaveToConfigFile(filename string) error {
cfg := ini.Empty()
err := ini.ReflectFrom(cfg, &struct {
Log *LogConfig
LiveRoom *LiveRoomConfig
Player *PlayerConfig
Provider *ProviderConfig
}{
Log: Log,
LiveRoom: LiveRoom,
Player: Player,
Provider: Provider,
})
if err != nil {
return err
cfgFile := ini.Empty()
for _, cfg := range Configs {
if err := cfgFile.Section(cfg.Name()).ReflectFrom(cfg); err != nil {
fmt.Println(err)
}
}
return cfg.SaveTo(filename)
return cfgFile.SaveTo(filename)
}

11
config/config_liveroom.go Normal file
View File

@@ -0,0 +1,11 @@
package config
type _LiveRoomConfig struct {
History []string
}
func (c *_LiveRoomConfig) Name() string {
return "LiveRoom"
}
var LiveRoom = &_LiveRoomConfig{History: []string{"9076804", "3819533"}}

17
config/config_log.go Normal file
View File

@@ -0,0 +1,17 @@
package config
import "github.com/sirupsen/logrus"
type _LogConfig struct {
Path string
Level logrus.Level
}
func (c *_LogConfig) Name() string {
return "Log"
}
var Log = &_LogConfig{
Path: "./log.txt",
Level: logrus.InfoLevel,
}

19
config/config_player.go Normal file
View File

@@ -0,0 +1,19 @@
package config
type _PlayerConfig struct {
Playlists []string
PlaylistsProvider []string
PlaylistIndex int
PlaylistRandom bool
}
func (c *_PlayerConfig) Name() string {
return "Player"
}
var Player = &_PlayerConfig{
Playlists: []string{"2382819181", "116746576", "646548465"},
PlaylistsProvider: []string{"netease", "netease", "netease"},
PlaylistIndex: 0,
PlaylistRandom: true,
}

15
config/config_provider.go Normal file
View File

@@ -0,0 +1,15 @@
package config
type _ProviderConfig struct {
Priority []string
LocalDir string
}
func (c *_ProviderConfig) Name() string {
return "Provider"
}
var Provider = &_ProviderConfig{
Priority: []string{"local", "netease", "kuwo", "bilibili"},
LocalDir: "./music",
}

View File

@@ -11,4 +11,5 @@ func TestCreate(t *testing.T) {
func TestLoad(t *testing.T) {
fmt.Println(Log.Path)
fmt.Println(Player.Playlists)
}

View File

@@ -1,94 +0,0 @@
package controller
import (
"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
}
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)
}
}

View File

@@ -20,6 +20,9 @@ func AddCommand(executors ...DanmuCommandExecutor) {
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)

View File

@@ -1,25 +0,0 @@
package controller
import (
"AynaLivePlayer/liveclient"
"strings"
)
var CommandDiange = &Diange{}
type Diange struct {
}
func (d Diange) Match(command string) bool {
for _, c := range []string{"点歌"} {
if command == c {
return true
}
}
return false
}
func (d Diange) Execute(command string, args []string, danmu *liveclient.DanmuMessage) {
keyword := strings.Join(args, " ")
Add(keyword, &danmu.User)
}

View File

@@ -19,15 +19,93 @@ func l() *logrus.Entry {
return logger.Logger.WithField("Module", MODULE_CONTROLLER)
}
func Initialize() {
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)
}
AddCommand(CommandDiange)
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 = ""
}
MainPlayer.ObserveProperty("idle-active", handleMpvIdlePlayNext)
UserPlaylist.Handler.RegisterA(player.EventPlaylistInsert, "controller.playnextwhenadd", handlePlaylistAdd)
MainPlayer.ObserveProperty("time-pos", handleLyricUpdate)
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)
}
MainPlayer.Start()
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() {
@@ -51,6 +129,10 @@ func SetDanmuClient(roomId string) {
Name: "controller.commandexecutor",
Handler: danmuCommandHandler,
})
LiveClient.Handler().RegisterA(
liveclient.EventMessageReceive,
"controller.danmu.handler",
danmuHandler)
l().Infof("setting live client for %s success", roomId)
}

23
controller/danmu.go Normal file
View File

@@ -0,0 +1,23 @@
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)
}
}

View File

@@ -14,14 +14,21 @@ var SystemPlaylist *player.Playlist
var LiveClient liveclient.LiveClient
var PlaylistManager []*player.Playlist
var CurrentLyric *player.Lyric
var CurrentMedia *player.Media
func Initialize() {
func init() {
MainPlayer = player.NewPlayer()
UserPlaylist = player.NewPlaylist("user", player.PlaylistConfig{RandomNext: false})
SystemPlaylist = player.NewPlaylist("system", player.PlaylistConfig{RandomNext: config.Player.PlaylistRandom})
PlaylistManager = make([]*player.Playlist, 0)
CurrentLyric = player.NewLyric("")
loadPlaylists()
MainPlayer.ObserveProperty("idle-active", handleMpvIdlePlayNext)
UserPlaylist.Handler.RegisterA(player.EventPlaylistInsert, "controller.playnextwhenadd", handlePlaylistAdd)
MainPlayer.ObserveProperty("time-pos", handleLyricUpdate)
MainPlayer.Start()
}
func loadPlaylists() {

19
controller/plugin.go Normal file
View File

@@ -0,0 +1,19 @@
package controller
type Plugin interface {
Name() string
Enable() 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)
}
}

View File

@@ -65,6 +65,7 @@ func ApplyUser(medias []*player.Media, user interface{}) {
}
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)

13
go.mod
View File

@@ -4,14 +4,27 @@ go 1.16
require (
fyne.io/fyne/v2 v2.1.4
github.com/BurntSushi/toml v0.4.1
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9
github.com/XiaoMengXinX/Music163Api-Go v0.1.26
github.com/antonfisher/nested-logrus-formatter v1.3.1
github.com/aynakeya/blivedm v0.1.3
github.com/aynakeya/go-mpv v0.0.4
github.com/go-ole/go-ole v1.2.6
github.com/go-resty/resty/v2 v2.7.0
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b
github.com/mitchellh/panicwrap v1.0.0
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cast v1.3.1
github.com/stretchr/testify v1.5.1
github.com/tidwall/gjson v1.14.1
github.com/urfave/cli/v2 v2.3.0
golang.org/x/mod v0.4.2
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
golang.org/x/tools v0.1.5
gopkg.in/ini.v1 v1.66.4
)

18
go.sum
View File

@@ -1,13 +1,17 @@
fyne.io/fyne/v2 v2.1.4 h1:bt1+28++kAzRzPB0GM2EuSV4cnl8rXNX4cjfd8G06Rc=
fyne.io/fyne/v2 v2.1.4/go.mod h1:p+E/Dh+wPW8JwR2DVcsZ9iXgR9ZKde80+Y+40Is54AQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9 h1:1ltqoej5GtaWF8jaiA49HwsZD459jqm9YFz9ZtMFpQA=
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
github.com/XiaoMengXinX/Music163Api-Go v0.1.26 h1:Nybor5okI8C0jzAiRvGfpLHdDrPqUbjx5kXWIZDX6pw=
github.com/XiaoMengXinX/Music163Api-Go v0.1.26/go.mod h1:kLU/CkLxKnEJFCge0URvQ0lHt6ImoG1/2aVeNbgV2RQ=
github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ=
github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -20,6 +24,7 @@ github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f h1:s0O46d8fPwk9kU4k1jj76w
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be h1:Z28GdQBfKOL8tNHjvaDn3wHDO7AzTRkmAXvHvnopp98=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q=
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
@@ -32,20 +37,29 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526 h1:NfuKjkj/Xc2z1xZIj+EmNCm5p1nKJPyw3F4E20usXvg=
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca h1:ozPUX9TKQZVek4lZWYRsQo7uS8vJ+q4OOHvRhHiCLfU=
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b h1:tLSDWcFhT0WRlnsFszh4iaFTexWF8mmccGTk88Siq7Q=
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
github.com/mitchellh/panicwrap v1.0.0 h1:67zIyVakCIvcs69A0FGfZjBdPleaonSgGlXRSRlb6fE=
github.com/mitchellh/panicwrap v1.0.0/go.mod h1:pKvZHwWrZowLUzftuFq7coarnxbBXU4aQh3N0BJOeeA=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
@@ -70,6 +84,7 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.3.8 h1:Nw158Q8QN+CPgTmVRByhVwapp8Mm1e2blinhmx4wx5E=
@@ -78,6 +93,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -104,9 +120,11 @@ golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=

20
gui/config_basic.go Normal file
View File

@@ -0,0 +1,20 @@
package gui
import (
"fyne.io/fyne/v2"
)
type bascicConfig struct{}
func (b bascicConfig) Title() string {
return "Basic"
}
func (b bascicConfig) Description() string {
return "Basic configuration"
}
func (b bascicConfig) Create() fyne.CanvasObject {
//TODO implement me
panic("implement me")
}

55
gui/config_layout.go Normal file
View File

@@ -0,0 +1,55 @@
package gui
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
)
type TestConfig struct {
}
func (t *TestConfig) Title() string {
return "Test Title"
}
func (T *TestConfig) Description() string {
return "Test Description"
}
func (t *TestConfig) CreatePanel() fyne.CanvasObject {
return widget.NewLabel("asdf")
}
func createConfigLayout() fyne.CanvasObject {
content := container.NewMax()
entryList := widget.NewList(
func() int {
return len(ConfigList)
},
func() fyne.CanvasObject {
return widget.NewLabel("AAAAAAAAAAAAAAAA")
},
func(id widget.ListItemID, object fyne.CanvasObject) {
object.(*widget.Label).SetText(ConfigList[id].Title())
})
entryList.OnSelected = func(id widget.ListItemID) {
desc := widget.NewRichTextFromMarkdown("## " + ConfigList[id].Title() + " \n\n" + ConfigList[id].Description())
for i := range desc.Segments {
if seg, ok := desc.Segments[i].(*widget.TextSegment); ok {
seg.Style.Alignment = fyne.TextAlignCenter
}
}
a := container.NewVScroll(ConfigList[id].CreatePanel())
content.Objects = []fyne.CanvasObject{
container.NewBorder(container.NewVBox(desc, widget.NewSeparator()), nil, nil, nil,
a),
}
content.Refresh()
}
return container.NewBorder(
nil, nil,
container.NewHBox(entryList, widget.NewSeparator()), nil,
content)
}

View File

@@ -12,8 +12,15 @@ import (
const MODULE_GUI = "GUI"
type ConfigLayout interface {
Title() string
Description() string
CreatePanel() fyne.CanvasObject
}
var App fyne.App
var MainWindow fyne.Window
var ConfigList = []ConfigLayout{}
func l() *logrus.Entry {
return logger.Logger.WithField("Module", MODULE_GUI)
@@ -37,6 +44,9 @@ func Initialize() {
container.NewTabItem("Playlist",
newPaddedBoarder(nil, nil, createPlaylists(), nil, createPlaylistMedias()),
),
container.NewTabItem("Config",
newPaddedBoarder(nil, nil, nil, nil, createConfigLayout()),
),
)
tabs.SetTabLocation(container.TabLocationTop)
@@ -46,3 +56,7 @@ func Initialize() {
MainWindow.Resize(fyne.NewSize(960, 480))
//MainWindow.SetFixedSize(true)
}
func AddConfigLayout(cfgs ...ConfigLayout) {
ConfigList = append(ConfigList, cfgs...)
}

View File

@@ -31,3 +31,46 @@ func createAsyncButton(btn *widget.Button, tapped func()) *widget.Button {
btn.OnTapped = createAsyncOnTapped(btn, tapped)
return btn
}
type ContextMenuButton struct {
widget.Button
menu *fyne.Menu
}
func (b *ContextMenuButton) Tapped(e *fyne.PointEvent) {
widget.ShowPopUpMenuAtPosition(b.menu, fyne.CurrentApp().Driver().CanvasForObject(b), e.AbsolutePosition)
}
func newContextMenuButton(label string, menu *fyne.Menu) *ContextMenuButton {
b := &ContextMenuButton{menu: menu}
b.Text = label
b.ExtendBaseWidget(b)
return b
}
type FixedSplitContainer struct {
*container.Split
}
func (f *FixedSplitContainer) Dragged(event *fyne.DragEvent) {
// do nothing
}
func (f *FixedSplitContainer) DragEnd() {
// do nothing
}
func newFixedSplitContainer(horizontal bool, leading, trailing fyne.CanvasObject) *FixedSplitContainer {
s := &container.Split{
Offset: 0.5, // Sensible default, can be overridden with SetOffset
Horizontal: horizontal,
Leading: leading,
Trailing: trailing,
}
fs := &FixedSplitContainer{
s,
}
fs.Split.BaseWidget.ExtendBaseWidget(s)
return fs
}

View File

@@ -51,7 +51,7 @@ func createPlayController() fyne.CanvasObject {
buttonsBox := container.NewCenter(
container.NewHBox(PlayController.ButtonPrev, PlayController.ButtonSwitch, PlayController.ButtonNext))
PlayController.Progress = widget.NewSlider(0, 10000)
PlayController.Progress = widget.NewSlider(0, 1000)
PlayController.CurrentTime = widget.NewLabel("0:00")
PlayController.TotalTime = widget.NewLabel("0:00")
progressItem := container.NewBorder(nil, nil, PlayController.CurrentTime, PlayController.TotalTime, PlayController.Progress)
@@ -113,10 +113,10 @@ func registerPlayControllerHandler() {
if controller.MainPlayer.ObserveProperty("percent-pos", func(property *mpv.EventProperty) {
if property.Data == nil {
PlayController.Progress.SetValue(0)
return
PlayController.Progress.Value = 0
} else {
PlayController.Progress.Value = property.Data.(mpv.Node).Value.(float64) * 10
}
PlayController.Progress.Value = property.Data.(mpv.Node).Value.(float64) * 100
PlayController.Progress.Refresh()
}) != nil {
l().Error("fail to register handler for progress bar with property percent-pos")
@@ -134,14 +134,14 @@ func registerPlayControllerHandler() {
//PlayController.Username.SetText("Username")
//PlayController.SetDefaultCover()
} else {
PlayController.Progress.Max = 10000
PlayController.Progress.Max = 1000
}
}) != nil {
l().Error("fail to register handler for progress bar with property idle-active")
}
PlayController.Progress.OnChanged = func(f float64) {
controller.Seek(f/100, false)
controller.Seek(f/10, false)
}
if controller.MainPlayer.ObserveProperty("time-pos", func(property *mpv.EventProperty) {

View File

@@ -7,9 +7,36 @@ import (
"fmt"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)
type playlistOperationButton struct {
widget.Button
Index int
menu *fyne.Menu
}
func (b *playlistOperationButton) Tapped(e *fyne.PointEvent) {
widget.ShowPopUpMenuAtPosition(b.menu, fyne.CurrentApp().Driver().CanvasForObject(b), e.AbsolutePosition)
}
func newPlaylistOperationButton() *playlistOperationButton {
b := &playlistOperationButton{Index: 0}
deleteItem := fyne.NewMenuItem("Delete", func() {
fmt.Println("delete", b.Index)
})
topItem := fyne.NewMenuItem("Top", func() {
controller.UserPlaylist.Move(b.Index, 0)
})
m := fyne.NewMenu("", deleteItem, topItem)
b.menu = m
b.Text = ""
b.Icon = theme.MoreHorizontalIcon()
b.ExtendBaseWidget(b)
return b
}
type PlaylistContainer struct {
Playlist *player.Playlist
List *widget.List
@@ -22,28 +49,31 @@ func createPlaylist() fyne.CanvasObject {
UserPlaylist.List = widget.NewList(
func() int {
//debug.PrintStack()
// todo: @4
//todo: @4
return UserPlaylist.Playlist.Size()
},
func() fyne.CanvasObject {
return container.NewBorder(nil, nil, widget.NewLabel("index"), widget.NewLabel("user"),
container.NewGridWithColumns(2,
return container.NewBorder(nil, nil, widget.NewLabel("index"), newPlaylistOperationButton(),
container.NewGridWithColumns(3,
newLabelWithWrapping("title", fyne.TextTruncate),
newLabelWithWrapping("artist", fyne.TextTruncate)))
newLabelWithWrapping("artist", fyne.TextTruncate),
newLabelWithWrapping("user", fyne.TextTruncate)))
},
func(id widget.ListItemID, object fyne.CanvasObject) {
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*widget.Label).SetText(
UserPlaylist.Playlist.Playlist[id].Title)
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(
UserPlaylist.Playlist.Playlist[id].Artist)
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[2].(*widget.Label).SetText(
UserPlaylist.Playlist.Playlist[id].ToUser().Name)
object.(*fyne.Container).Objects[1].(*widget.Label).SetText(fmt.Sprintf("%d", id))
object.(*fyne.Container).Objects[2].(*widget.Label).SetText(UserPlaylist.Playlist.Playlist[id].ToUser().Name)
object.(*fyne.Container).Objects[2].(*playlistOperationButton).Index = id
})
registerPlaylistHandler()
return container.NewBorder(
container.NewBorder(nil, nil,
widget.NewLabel("#"), widget.NewLabel("User"),
container.NewGridWithColumns(2, widget.NewLabel("Title"), widget.NewLabel("Artist"))),
widget.NewLabel("#"), widget.NewLabel("OPs"),
container.NewGridWithColumns(3, widget.NewLabel("Title"), widget.NewLabel("Artist"), widget.NewLabel("User"))),
widget.NewSeparator(),
nil, nil,
UserPlaylist.List,
@@ -52,6 +82,9 @@ 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()
UserPlaylist.List.Refresh()
UserPlaylist.Playlist.Lock.RUnlock()
})
}

View File

@@ -26,6 +26,7 @@ func createRoomController() fyne.CanvasObject {
controller.SetDanmuClient(RoomController.Input.Text)
if controller.LiveClient == nil {
RoomController.Status.SetText("Set Failed")
RoomController.ConnectBtn.Enable()
RoomController.Status.Refresh()
return
}

3685
log.txt

File diff suppressed because it is too large Load Diff

View File

@@ -23,6 +23,12 @@ type PlaylistUpdateEvent struct {
Playlist *Playlist
}
func newPlaylistUpdateEvent(playlist *Playlist) PlaylistUpdateEvent {
return PlaylistUpdateEvent{
Playlist: playlist,
}
}
type PlayEvent struct {
Media *Media
}

Binary file not shown.

View File

@@ -90,7 +90,7 @@ func (p *Player) Play(media *Media) error {
return err
}
}
p.l().Debug("mpv command load file", media)
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

View File

@@ -26,7 +26,7 @@ type Playlist struct {
Playlist []*Media
Handler *event.Handler
Meta interface{}
lock sync.RWMutex
Lock sync.RWMutex
}
func NewPlaylist(name string, config PlaylistConfig) *Playlist {
@@ -57,33 +57,32 @@ func (p *Playlist) Pop() *Media {
p.l().Warn("pop first media failed, no media left in the playlist")
return nil
}
p.lock.Lock()
p.Lock.Lock()
media := p.Playlist[0]
p.Playlist = p.Playlist[1:]
p.lock.Unlock()
p.Lock.Unlock()
defer p.Handler.CallA(EventPlaylistUpdate, PlaylistUpdateEvent{Playlist: p})
return media
}
func (p *Playlist) Replace(medias []*Media) {
p.lock.Lock()
p.Lock.Lock()
p.Playlist = medias
p.Index = 0
p.lock.Unlock()
p.Lock.Unlock()
p.Handler.CallA(EventPlaylistUpdate, PlaylistUpdateEvent{Playlist: p})
return
}
func (p *Playlist) Push(media *Media) {
p.Insert(-1, media)
defer p.Handler.CallA(EventPlaylistUpdate, PlaylistUpdateEvent{Playlist: p})
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().Debug("media=", *media)
p.l().Debugf("media= %s", media.Title)
e := event.Event{
Id: EventPlaylistPreInsert,
Cancelled: false,
@@ -98,7 +97,7 @@ func (p *Playlist) Insert(index int, media *Media) {
p.l().Info("insert new media has been cancelled by handler")
return
}
p.lock.Lock()
p.Lock.Lock()
if index > p.Size() {
index = p.Size()
}
@@ -110,7 +109,7 @@ func (p *Playlist) Insert(index int, media *Media) {
p.Playlist[i] = p.Playlist[i-1]
}
p.Playlist[index] = media
p.lock.Unlock()
p.Lock.Unlock()
defer func() {
p.Handler.Call(&event.Event{
Id: EventPlaylistInsert,
@@ -125,6 +124,52 @@ func (p *Playlist) Insert(index int, media *Media) {
}()
}
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 {

125
plugin/diange/diange.go Normal file
View File

@@ -0,0 +1,125 @@
package diange
import (
"AynaLivePlayer/config"
"AynaLivePlayer/controller"
"AynaLivePlayer/gui"
"AynaLivePlayer/liveclient"
"AynaLivePlayer/logger"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/widget"
"github.com/sirupsen/logrus"
"strings"
"time"
)
const MODULE_CMD_DIANGE = "CMD.DianGe"
func l() *logrus.Entry {
return logger.Logger.WithField("Module", MODULE_CMD_DIANGE)
}
type Diange struct {
UserPermission bool
PrivilegePermission bool
AdminPermission bool
QueueMax int
UserCoolDown int
CustomCMD string
cooldowns map[string]int
panel fyne.CanvasObject
}
func NewDiange() *Diange {
return &Diange{
UserPermission: true,
PrivilegePermission: true,
AdminPermission: true,
QueueMax: 128,
UserCoolDown: -1,
CustomCMD: "add",
cooldowns: make(map[string]int),
}
}
func (d *Diange) Name() string {
return "Diange"
}
func (d *Diange) Enable() error {
config.LoadConfig(d)
controller.AddCommand(d)
gui.AddConfigLayout(d)
return nil
}
func (d *Diange) Match(command string) bool {
for _, c := range []string{"点歌", d.CustomCMD} {
if command == c {
return true
}
}
return false
}
func (d *Diange) Execute(command string, args []string, danmu *liveclient.DanmuMessage) {
// if queue is full, return
if controller.UserPlaylist.Size() >= d.QueueMax {
l().Info("Queue is full, ignore diange")
return
}
// if in user cool down, return
ct := int(time.Now().Unix())
if (ct - d.cooldowns[danmu.User.Uid]) <= d.UserCoolDown {
l().Infof("User %s(%s) still in cool down period, diange failed", danmu.User.Username, danmu.User.Uid)
return
}
keyword := strings.Join(args, " ")
perm := d.UserPermission
l().Trace("user permission check: ", perm)
perm = perm || (d.PrivilegePermission && (danmu.User.Privilege > 0))
l().Trace("privilege permission check: ", perm)
perm = perm || (d.AdminPermission && (danmu.User.Admin))
l().Trace("admin permission check: ", perm)
if perm {
// reset cool down
d.cooldowns[danmu.User.Uid] = ct
controller.Add(keyword, &danmu.User)
}
}
func (d *Diange) Title() string {
return "Diange"
}
func (d *Diange) Description() string {
return "Basic diange configuration"
}
func (d *Diange) CreatePanel() fyne.CanvasObject {
if d.panel != nil {
return d.panel
}
dgPerm := container.NewHBox(
widget.NewLabel("点歌权限"),
widget.NewCheckWithData("用户", binding.BindBool(&d.UserPermission)),
widget.NewCheckWithData("舰长", binding.BindBool(&d.PrivilegePermission)),
widget.NewCheckWithData("管理员", binding.BindBool(&d.AdminPermission)),
)
dgQueue := container.NewBorder(nil, nil,
widget.NewLabel("最大点歌数"), nil,
widget.NewEntryWithData(binding.IntToString(binding.BindInt(&d.QueueMax))),
)
dgCoolDown := container.NewBorder(nil, nil,
widget.NewLabel("点歌冷却时间"), nil,
widget.NewEntryWithData(binding.IntToString(binding.BindInt(&d.UserCoolDown))),
)
dgShortCut := container.NewBorder(nil, nil,
widget.NewLabel("自定义命令 (默认的依然可用)"), nil,
widget.NewEntryWithData(binding.BindString(&d.CustomCMD)),
)
d.panel = container.NewVBox(dgPerm, dgQueue, dgCoolDown, dgShortCut)
return d.panel
}

101
plugin/qiege/qiege.go Normal file
View File

@@ -0,0 +1,101 @@
package qiege
import (
"AynaLivePlayer/config"
"AynaLivePlayer/controller"
"AynaLivePlayer/gui"
"AynaLivePlayer/liveclient"
"AynaLivePlayer/logger"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/widget"
"github.com/sirupsen/logrus"
)
const MODULE_CMD_QieGE = "CMD.QieGe"
func l() *logrus.Entry {
return logger.Logger.WithField("Module", MODULE_CMD_QieGE)
}
type Qiege struct {
UserPermission bool
PrivilegePermission bool
AdminPermission bool
CustomCMD string
panel fyne.CanvasObject
}
func NewQiege() *Qiege {
return &Qiege{
UserPermission: true,
PrivilegePermission: true,
AdminPermission: true,
CustomCMD: "skip",
}
}
func (d *Qiege) Name() string {
return "Qiege"
}
func (d *Qiege) Enable() error {
config.LoadConfig(d)
controller.AddCommand(d)
gui.AddConfigLayout(d)
return nil
}
func (d *Qiege) Match(command string) bool {
for _, c := range []string{"切歌", d.CustomCMD} {
if command == c {
return true
}
}
return false
}
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()
return
}
}
if d.PrivilegePermission && danmu.User.Privilege > 0 {
controller.PlayNext()
return
}
if d.AdminPermission && danmu.User.Admin {
controller.PlayNext()
return
}
}
func (d *Qiege) Title() string {
return "Qiege"
}
func (d *Qiege) Description() string {
return "Basic Qiege configuration"
}
func (d *Qiege) CreatePanel() fyne.CanvasObject {
if d.panel != nil {
return d.panel
}
dgPerm := container.NewHBox(
widget.NewLabel("切歌权限"),
widget.NewCheckWithData("切自己", binding.BindBool(&d.UserPermission)),
widget.NewCheckWithData("舰长", binding.BindBool(&d.PrivilegePermission)),
widget.NewCheckWithData("管理员", binding.BindBool(&d.AdminPermission)),
)
qgShortCut := container.NewBorder(nil, nil,
widget.NewLabel("自定义命令 (默认的依然可用)"), nil,
widget.NewEntryWithData(binding.BindString(&d.CustomCMD)),
)
d.panel = container.NewVBox(dgPerm, qgShortCut)
return d.panel
}

View File

@@ -2,28 +2,36 @@ package provider
import (
"AynaLivePlayer/player"
"AynaLivePlayer/util"
"fmt"
"github.com/tidwall/gjson"
"html"
"net/url"
"regexp"
"strings"
)
type Kuwo struct {
InfoApi string
FileApi string
SearchCookie string
SearchApi string
InfoApi string
FileApi string
SearchCookie string
SearchApi string
LyricApi string
PlaylistApi string
PlaylistRegex0 *regexp.Regexp
PlaylistRegex1 *regexp.Regexp
}
func _newKuwo() *Kuwo {
return &Kuwo{
InfoApi: "http://www.kuwo.cn/api/www/music/musicInfo?mid=%s&httpsStatus=1",
//FileApi: "http://www.kuwo.cn/api/v1/www/music/playUrl?mid=%d&type=music&httpsStatus=1",
FileApi: "http://antiserver.kuwo.cn/anti.s?type=convert_url&format=mp3&response=url&rid=MUSIC_%s",
SearchCookie: "http://kuwo.cn/search/list?key=%s",
SearchApi: "http://www.kuwo.cn/api/www/search/searchMusicBykeyWord?key=%s&pn=%d&rn=%d",
FileApi: "http://antiserver.kuwo.cn/anti.s?type=convert_url&format=mp3&response=url&rid=MUSIC_%s",
SearchCookie: "http://kuwo.cn/search/list?key=%s",
SearchApi: "http://www.kuwo.cn/api/www/search/searchMusicBykeyWord?key=%s&pn=%d&rn=%d",
LyricApi: "http://m.kuwo.cn/newh5/singles/songinfoandlrc?musicId=%s",
PlaylistApi: "http://www.kuwo.cn/api/www/playlist/playListInfo?pid=%s&pn=%d&rn=%d&httpsStatus=1",
PlaylistRegex0: regexp.MustCompile("[0-9]+"),
PlaylistRegex1: regexp.MustCompile("playlist/[0-9]+"),
}
}
@@ -39,21 +47,38 @@ func (k *Kuwo) GetName() string {
}
func (k *Kuwo) FormatPlaylistUrl(uri string) string {
var id string
id = k.PlaylistRegex0.FindString(uri)
if id != "" {
return id
}
id = k.PlaylistRegex1.FindString(uri)
if id != "" {
return id[9:]
}
return ""
}
//func (k *Kuwo) _kuwoGet(url string) string {
// searchCookie, err := httpHead(fmt.Sprintf(k.SearchCookie, "any"), nil)
// if err != nil {
// return ""
// }
// kwToken, ok := util.SliceString(regexp.MustCompile("kw_token=([^;])*;").FindString(searchCookie.Header().Get("set-cookie")), 9, -1)
// if !ok {
// return ""
// }
// return httpGetString(url, map[string]string{
// "cookie": "kw_token=" + kwToken,
// "csrf": kwToken,
// "referer": "http://www.kuwo.cn/",
// })
//}
func (k *Kuwo) _kuwoGet(url string) string {
searchCookie, err := httpHead(fmt.Sprintf(k.SearchCookie, "any"), nil)
if err != nil {
return ""
}
kwToken, ok := util.SliceString(regexp.MustCompile("kw_token=([^;])*;").FindString(searchCookie.Header().Get("set-cookie")), 9, -1)
if !ok {
return ""
}
return httpGetString(url, map[string]string{
"cookie": "kw_token=" + kwToken,
"csrf": kwToken,
"cookie": "kw_token=" + "95MWTYC4FP",
"csrf": "95MWTYC4FP",
"referer": "http://www.kuwo.cn/",
})
}
@@ -106,10 +131,59 @@ func (k *Kuwo) UpdateMediaUrl(media *player.Media) error {
}
func (k *Kuwo) UpdateMediaLyric(media *player.Media) error {
fmt.Println(k._kuwoGet("https://player.kuwo.cn/webmusic/st/getNewMuiseByRid?rid=MUSIC_22804772"))
result := httpGetString(fmt.Sprintf(k.LyricApi, media.Meta.(Meta).Id), nil)
if result == "" {
return ErrorExternalApi
}
lrcs := make([]string, 0)
gjson.Parse(result).Get("data.lrclist").ForEach(func(key, value gjson.Result) bool {
lrcs = append(lrcs, fmt.Sprintf("[00:%s]%s", value.Get("time").String(), value.Get("lineLyric").String()))
return true
})
media.Lyric = strings.Join(lrcs, "\n")
return nil
}
func (k *Kuwo) GetPlaylist(meta Meta) ([]*player.Media, error) {
return nil, ErrorExternalApi
medias := make([]*player.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))
if resp == "" {
break
}
//fmt.Println(resp[:100])
jresp = gjson.Parse(resp)
//fmt.Println(jresp.Get("code").String())
if jresp.Get("code").String() != "200" {
break
}
cnt := int(jresp.Get("data.total").Int())
//fmt.Println(cnt)
//fmt.Println(len(jresp.Get("data.musicList").Array()))
jresp.Get("data.musicList").ForEach(func(key, value gjson.Result) bool {
medias = append(
medias,
&player.Media{
Title: html.UnescapeString(value.Get("name").String()),
Artist: value.Get("artist").String(),
Cover: value.Get("pic").String(),
Album: value.Get("album").String(),
Meta: Meta{
Name: k.GetName(),
Id: value.Get("rid").String(),
},
})
return true
})
if cnt <= i*100 {
break
}
}
if len(medias) == 0 {
return nil, ErrorExternalApi
}
return medias, nil
}

View File

@@ -68,3 +68,21 @@ func TestKuwo_UpdateMediaLyric(t *testing.T) {
fmt.Println(err)
fmt.Println(media.Lyric)
}
func TestKuwo_GetPlaylist(t *testing.T) {
var api MediaProvider = KuwoAPI
playlist, err := api.GetPlaylist(Meta{
Name: api.GetName(),
//Id: "1082685104",
Id: "2959147566",
})
if err != nil {
fmt.Println(err)
return
}
fmt.Println(len(playlist))
for _, media := range playlist {
fmt.Println(media.Title, media.Artist, media.Album)
}
}

View File

@@ -75,35 +75,41 @@ func (n *Netease) GetPlaylist(meta Meta) ([]*player.Media, error) {
if cnt == 0 {
return nil, ErrorExternalApi
}
ids := make([]int, len(result.Playlist.TrackIds))
for i := 0; i < cnt; i++ {
ids[i] = result.Playlist.TrackIds[i].Id
}
result2, err := neteaseApi.GetSongDetail(
n.ReqData,
ids)
if err != nil || result.Code != 200 {
return nil, ErrorExternalApi
}
cnt = len(result2.Songs)
if cnt == 0 {
return nil, ErrorExternalApi
}
medias := make([]*player.Media, cnt)
for i := 0; i < cnt; i++ {
medias[i] = &player.Media{
Title: result2.Songs[i].Name,
Artist: _neteaseGetArtistNames(result2.Songs[i]),
Cover: result2.Songs[i].Al.PicUrl,
Album: result2.Songs[i].Al.Name,
Url: "",
Header: nil,
User: nil,
Meta: Meta{
Name: n.GetName(),
Id: strconv.Itoa(result2.Songs[i].Id),
},
medias := make([]*player.Media, 0, cnt)
for index := 0; index < len(ids); index += 1000 {
result2, err := neteaseApi.GetSongDetail(
n.ReqData,
ids[index:util.IntMin(index+1000, len(ids))])
if err != nil || result2.Code != 200 {
break
}
cnt = len(result2.Songs)
if cnt == 0 {
break
}
for i := 0; i < cnt; i++ {
medias = append(medias, &player.Media{
Title: result2.Songs[i].Name,
Artist: _neteaseGetArtistNames(result2.Songs[i]),
Cover: result2.Songs[i].Al.PicUrl,
Album: result2.Songs[i].Al.Name,
Url: "",
Header: nil,
User: nil,
Meta: Meta{
Name: n.GetName(),
Id: strconv.Itoa(result2.Songs[i].Id),
},
})
}
}
if len(medias) == 0 {
return nil, ErrorExternalApi
}
return medias, nil
}

View File

@@ -61,12 +61,14 @@ func TestNetease_GetPlaylist(t *testing.T) {
var api MediaProvider = NeteaseAPI
playlist, err := api.GetPlaylist(Meta{
Name: api.GetName(),
Id: "2520739691",
//Id: "2520739691",
Id: "2382819181",
})
if err != nil {
fmt.Println(err)
return
}
fmt.Println(len(playlist))
for _, media := range playlist {
fmt.Println(media.Title, media.Artist, media.Album)
}

View File

@@ -2,9 +2,18 @@
- went idle and insert new item race condition
- @3 fix handler execution (maybe priority)
- @4 list refresh
- @5 delete optimization
- 歌词来源
- 设置界面
- kuwo歌单
- 文本输出
- web输出
- 进入beta版本
- 进入beta版本
----
Finished
- 2022.6.25: 设置界面
- 2022.6.25: @6 bug, race condition, playlist size changed during playlist update.
- 2022.6.23: 用户歌单操作

8
util/integer.go Normal file
View File

@@ -0,0 +1,8 @@
package util
func IntMin(x, y int) int {
if x < y {
return x
}
return y
}