ui界面优化,event handler优化-新增任务池模式,歌词加载优化,新房间管理(可以自动连接) 本地音频搜索算法优化,

This commit is contained in:
Aynakeya
2022-12-25 01:27:56 -08:00
parent 9ec4057412
commit 1e18ca1ff2
32 changed files with 429 additions and 359 deletions

View File

@@ -10,14 +10,16 @@ endif
ifeq ($(OS), Windows_NT)
EXECUTABLE=$(NAME).exe
SCRIPTPATH = .\assets\scripts\windows
else
EXECUTABLE=$(NAME)
SCRIPTPATH = ./assets/scripts/linux
endif
${EXECUTABLE}:
gui: bundle
go build -o $(EXECUTABLE) -ldflags -H=windowsgui ./app/gui/main.go
run:
run: bundle
go run ./app/gui/main.go
clear:
@@ -29,20 +31,24 @@ bundle:
fyne bundle --append --name resFontMSYaHei --package resource ./assets/msyh.ttc >> ./resource/bundle.go
fyne bundle --append --name resFontMSYaHeiBold --package resource ./assets/msyhbd.ttc >> ./resource/bundle.go
release: ${EXECUTABLE} bundle
release: gui
-mkdir release
ifeq ($(OS), Windows_NT)
COPY .\$(EXECUTABLE) .\release\$(EXECUTABLE)
COPY .\webtemplates.json .\release\webtemplates.json
COPY .\assets\translation.json .\release\assets\translation.json
COPY LICENSE.md .\release\LICENSE.md
XCOPY .\assets .\release\assets /s /e /i /y /q
XCOPY .\assets\scripts\windows\* .\release\ /k /i /y /q
XCOPY .\assets\webinfo .\release\assets\webinfo /s /e /i /y /q
XCOPY .\music .\release\music /s /e /i /y /q
XCOPY .\template .\release\template /s /e /i /y /q
else
cp ./$(EXECUTABLE) ./release/$(EXECUTABLE)
cp ./webtemplates.json ./release/webtemplates.json
cp ./assets/translation.json ./release/assets/translation.json
cp LICENSE.md ./release/LICENSE.md
cp -r ./assets ./release/assest
cp ./assets/scripts/linux/* ./release/
cp -r ./assets/webinfo ./release/assest/webinfo
cp -r ./music ./release/music
cp -r ./template ./release/template
endif

27
app/wrapwordbug/main.go Normal file
View File

@@ -0,0 +1,27 @@
package main
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
)
func main() {
a := app.New()
w := a.NewWindow("Hello World")
texts := make([]fyne.CanvasObject, 1)
for i := 0; i < len(texts); i++ {
l := widget.NewLabelWithStyle(
" AAAA",
fyne.TextAlignCenter, fyne.TextStyle{})
l.Wrapping = fyne.TextWrapWord
texts[i] = l
}
vbox := container.NewVBox(texts...)
scroll := container.NewScroll(vbox)
w.SetContent(scroll)
w.Resize(fyne.NewSize(360, 540))
w.ShowAndRun()
}

View File

@@ -0,0 +1 @@
taskkill /IM "AynaLivePlayer.exe" /F

View File

@@ -0,0 +1 @@
netsh winsock reset

69
common/util/generic.go Normal file
View File

@@ -0,0 +1,69 @@
package util
import (
"golang.org/x/exp/constraints"
)
func Slice[T any](arr []T, from int, to int) []T {
l := len(arr)
to = Min(to, l)
from = Min(from, l)
if to <= 0 {
to = l + to
if to <= 0 {
return []T{}
}
}
if from < 0 {
from = l + from
if from < 0 {
from = 0
}
}
if to <= from {
return []T{}
}
return arr[from:to]
}
func SliceCopy[T any](src []T) []T {
x := make([]T, len(src))
copy(x, src)
return x
}
func SliceContains[T comparable](s []T, e T) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
func TernaryOp[T any](cond bool, a T, b T) T {
if cond {
return a
}
return b
}
func Min[T constraints.Ordered](arr ...T) T {
min := arr[0]
for _, a := range arr {
if a < min {
min = a
}
}
return min
}
func Max[T constraints.Ordered](arr ...T) T {
max := arr[0]
for _, a := range arr {
if a > max {
max = a
}
}
return max
}

View File

@@ -1,7 +0,0 @@
package util
import "image"
func ReadImage(path string) image.Image {
return nil
}

View File

@@ -5,18 +5,7 @@ import (
"strconv"
)
func SliceString(str string, from int, to int) (string, bool) {
sList := []rune(str)
if to <= 0 {
to = len(sList) + to
}
if from >= len(sList) || to > len(sList) {
return "", false
}
return string(sList[from:to]), true
}
func LenString(str string) int {
func StrLen(str string) int {
return len([]rune(str))
}
@@ -25,29 +14,106 @@ func StringNormalize(str string, min int, max int) string {
return fmt.Sprintf(fmtStr, str)
}
func StringSliceContains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
func StringToInt(s string) int {
func Atoi(s string) int {
i, _ := strconv.Atoi(s)
return i
}
func StringSliceCopy(src []string) []string {
x := make([]string, len(src))
copy(x, src)
return x
}
func GetOrDefault(s string, def string) string {
if s == "" {
return def
}
return s
}
func LevenshteinDistance(s1 string, s2 string) int {
// support unicode
r1 := []rune(s1)
r2 := []rune(s2)
r1l := len(r1)
r2l := len(r2)
if r1l == 0 || r2l == 0 {
return Max(r1l, r2l)
}
previous := make([]int, r2l+1)
current := make([]int, r2l+1)
for i := 0; i <= r2l; i++ {
previous[i] = i
}
for i := 1; i <= r1l; i++ {
current[0] = i
for j := 1; j <= r2l; j++ {
subCost := 1
if r1[i-1] == r2[j-1] {
subCost = 0
}
// current[j] = min( insertCost,deleteCost, subCost)
current[j] = Min(current[j-1]+1, previous[j]+1, previous[j-1]+subCost)
}
current, previous = previous, current
}
return previous[r2l]
}
func WeightedLevenshteinDistance(s1 string, s2 string, ins, del, repl int) int {
// support unicode
r1 := []rune(s1)
r2 := []rune(s2)
r1l := len(r1)
r2l := len(r2)
if r1l == 0 || r2l == 0 {
return Max(r1l, r2l)
}
previous := make([]int, r2l+1)
current := make([]int, r2l+1)
for i := 0; i <= r2l; i++ {
previous[i] = i
}
for i := 1; i <= r1l; i++ {
current[0] = i
for j := 1; j <= r2l; j++ {
subCost := 1
if r1[i-1] == r2[j-1] {
subCost = 0
}
// current[j] = min( insertCost,deleteCost, subCost)
current[j] = Min(current[j-1]+1*ins, previous[j]+1*del, previous[j-1]+subCost*repl)
}
current, previous = previous, current
}
return previous[r2l]
}
func LongestCommonString(s1 string, s2 string) string {
// support unicode
r1 := []rune(s1)
r2 := []rune(s2)
r1l := len(r1)
r2l := len(r2)
if r1l == 0 || r2l == 0 {
return ""
}
previous := make([]int, r2l+1)
current := make([]int, r2l+1)
max := 0
maxIndex := 0
for i := 1; i <= r1l; i++ {
for j := 1; j <= r2l; j++ {
if r1[i-1] == r2[j-1] {
current[j] = previous[j-1] + 1
if current[j] > max {
max = current[j]
maxIndex = i
}
} else {
current[j] = 0
}
}
current, previous = previous, current
}
return string(r1[maxIndex-max : maxIndex])
}

View File

@@ -0,0 +1,32 @@
package util
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestLevenshteinDistance(t *testing.T) {
assert.Equal(t, 3, LevenshteinDistance("kitten", "sitting"))
assert.Equal(t, 0, LevenshteinDistance("kitten", "kitten"))
assert.Equal(t, 1, LevenshteinDistance("kitten", "kittens"))
assert.Equal(t, 2, LevenshteinDistance("kitten", "kitt"))
assert.Greater(t, LevenshteinDistance("夜曲 周杰伦/方文山", "夜曲 周杰伦"), LevenshteinDistance("夜曲 翻唱A", "夜曲 周杰伦"))
assert.Greater(t,
WeightedLevenshteinDistance("Mojito Tommy Hong", "Mojito 周杰伦", 1, 1, 3),
WeightedLevenshteinDistance("Mojito 周杰伦", "Mojito 周杰伦", 1, 1, 3))
assert.Greater(t,
WeightedLevenshteinDistance("默 (Live) 李荣浩/周杰伦", "Mojito 周杰伦", 1, 1, 3),
WeightedLevenshteinDistance("Mojito 周杰伦", "Mojito 周杰伦", 1, 1, 3))
assert.Greater(t,
WeightedLevenshteinDistance("布拉格广场 周杰伦", "Mojito 周杰伦", 1, 1, 3),
WeightedLevenshteinDistance("Mojito 周杰伦", "Mojito 周杰伦", 1, 1, 3))
assert.Greater(t,
WeightedLevenshteinDistance("Mojito翻自 cover 周杰伦)野猪佩奇", "Mojito 周杰伦", 1, 1, 3),
WeightedLevenshteinDistance("Mojito 周杰伦", "Mojito 周杰伦", 1, 1, 3))
//assert.Less(t, WeightedLevenshteinDistance("夜曲 周杰伦/方文山", "夜曲 周杰伦",1,1,3), WeightedLevenshteinDistance("夜曲 翻唱A", "夜曲 周杰伦",1,1,3))
}
func TestLongestCommonString(t *testing.T) {
assert.Equal(t, "itt", LongestCommonString("kitten", "sitting"))
assert.Equal(t, "布拉格广场", LongestCommonString("布拉格广场 周杰伦", "布拉格广场"))
}

View File

@@ -7,6 +7,7 @@ import (
"AynaLivePlayer/model"
"AynaLivePlayer/player"
"AynaLivePlayer/repo/provider"
"errors"
)
type PlayController struct {
@@ -122,29 +123,33 @@ func (pc *PlayController) PlayNext() {
media = pc.playlist.GetDefault().Next().Copy()
media.User = controller.PlaylistUser
}
pc.Play(media)
_ = pc.Play(media)
}
func (pc *PlayController) Play(media *model.Media) {
func (pc *PlayController) Play(media *model.Media) error {
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
lg.Warn("[PlayController] prepare media failed, try play next")
//pc.PlayNext()
return errors.New("prepare media failed")
}
pc.eventManager.CallA(model.EventPlay, model.PlayEvent{
Media: media,
})
pc.playing = media
pc.playlist.AddToHistory(media)
if err := pc.player.Play(media); err != nil {
lg.Warn("[PlayController] play failed", err)
return
return errors.New("player play failed")
}
pc.eventManager.CallA(model.EventPlay, model.PlayEvent{
pc.eventManager.CallA(model.EventPlayed, model.PlayEvent{
Media: media,
})
pc.lyric.Reload(media.Lyric)
// reset
media.Url = ""
return nil
}
func (pc *PlayController) Add(keyword string, user interface{}) {
@@ -178,6 +183,7 @@ func (pc *PlayController) AddWithProvider(keyword string, pname string, user int
lg.Infof("[PlayController] search for %s, got no result", keyword)
return
}
media = medias[0]
}
media.User = user

View File

@@ -4,7 +4,7 @@ import (
"AynaLivePlayer/config"
"AynaLivePlayer/controller"
"AynaLivePlayer/model"
provider2 "AynaLivePlayer/repo/provider"
"AynaLivePlayer/repo/provider"
)
type ProviderController struct {
@@ -23,7 +23,7 @@ func NewProviderController() controller.IProviderController {
LocalDir: "./music",
}
config.LoadConfig(p)
provider2.NewLocal(p.LocalDir)
provider.NewLocal(p.LocalDir)
return p
}
@@ -35,21 +35,21 @@ func (pc *ProviderController) PrepareMedia(media *model.Media) error {
var err error
if media.Title == "" || !media.Cover.Exists() {
lg.Trace("fetching media info")
if err = provider2.UpdateMedia(media); err != nil {
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 = provider2.UpdateMediaUrl(media); err != nil {
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 = provider2.UpdateMediaLyric(media); err != nil {
if err = provider.UpdateMediaLyric(media); err != nil {
lg.Warn("fail to prepare media when lyric", err)
}
}
@@ -59,12 +59,12 @@ func (pc *ProviderController) PrepareMedia(media *model.Media) error {
func (pc *ProviderController) MediaMatch(keyword string) *model.Media {
lg.Infof("Match media for %s", keyword)
for _, p := range pc.Priority {
if pr, ok := provider2.Providers[p]; ok {
if pr, ok := provider.Providers[p]; ok {
m := pr.MatchMedia(keyword)
if m == nil {
continue
}
if err := provider2.UpdateMedia(m); err == nil {
if err := provider.UpdateMedia(m); err == nil {
return m
}
} else {
@@ -77,7 +77,7 @@ func (pc *ProviderController) MediaMatch(keyword string) *model.Media {
func (pc *ProviderController) Search(keyword string) ([]*model.Media, error) {
lg.Infof("Search for %s", keyword)
for _, p := range pc.Priority {
if pr, ok := provider2.Providers[p]; ok {
if pr, ok := provider.Providers[p]; ok {
r, err := pr.Search(keyword)
if err != nil {
lg.Warn("Provider %s return err", err)
@@ -88,22 +88,22 @@ func (pc *ProviderController) Search(keyword string) ([]*model.Media, error) {
lg.Warnf("Provider %s not exist", p)
}
}
return nil, provider2.ErrorNoSuchProvider
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 := provider2.Providers[p]; ok {
if pr, ok := provider.Providers[p]; ok {
r, err := pr.Search(keyword)
return r, err
}
lg.Warnf("Provider %s not exist", p)
return nil, provider2.ErrorNoSuchProvider
return nil, provider.ErrorNoSuchProvider
}
func (pc *ProviderController) PreparePlaylist(playlist controller.IPlaylist) error {
lg.Debug("Prepare playlist ", playlist.Name())
medias, err := provider2.GetPlaylist(&playlist.Model().Meta)
medias, err := provider.GetPlaylist(&playlist.Model().Meta)
if err != nil {
lg.Warn("prepare playlist failed ", err)
return err

View File

@@ -11,7 +11,7 @@ type IPlayController interface {
GetPlaying() *model.Media
GetPlayer() player.IPlayer
PlayNext()
Play(media *model.Media)
Play(media *model.Media) error
Add(keyword string, user interface{})
AddWithProvider(keyword string, provider string, user interface{})
Seek(position float64, absolute bool)

9
go.mod
View File

@@ -4,7 +4,7 @@ go 1.19
require (
fyne.io/fyne/v2 v2.2.4
github.com/XiaoMengXinX/Music163Api-Go v0.1.26
github.com/XiaoMengXinX/Music163Api-Go v0.1.29
github.com/antonfisher/nested-logrus-formatter v1.3.1
github.com/aynakeya/blivedm v0.1.6
github.com/aynakeya/go-mpv v0.0.6
@@ -13,11 +13,14 @@ require (
github.com/gorilla/websocket v1.5.0
github.com/jinzhu/copier v0.3.5
github.com/magiconair/properties v1.8.5
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/sirupsen/logrus v1.8.1
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/spf13/cast v1.5.0
github.com/stretchr/testify v1.7.2
github.com/tidwall/gjson v1.14.3
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459
golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15
gopkg.in/ini.v1 v1.66.4
)
@@ -36,11 +39,9 @@ require (
github.com/google/uuid v1.3.0 // indirect
github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect
github.com/stretchr/testify v1.7.2 // indirect
github.com/tevino/abool v1.2.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
@@ -48,7 +49,7 @@ require (
golang.org/x/image v0.0.0-20220601225756-64ec528b34cd // indirect
golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee // indirect
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b // indirect
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
golang.org/x/sys v0.1.0 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 // indirect

13
go.sum
View File

@@ -44,8 +44,8 @@ fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93/go.mod h1:oM2AQqGJ1AMo4nNq
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
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/XiaoMengXinX/Music163Api-Go v0.1.29 h1:c7ekfgo4qgEJ3Wjm9rMhGm7ggN8XqbD1idQka4unJ+Q=
github.com/XiaoMengXinX/Music163Api-Go v0.1.29/go.mod h1:kLU/CkLxKnEJFCge0URvQ0lHt6ImoG1/2aVeNbgV2RQ=
github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ=
@@ -157,7 +157,7 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -356,6 +356,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 h1:5oN1Pz/eDhCpbMbLstvIPa0b/BEQo6g6nwV3pLjfM6w=
golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20220601225756-64ec528b34cd h1:9NbNcTg//wfC5JskFW4Z3sqwVnjmJKHxLAol1bW2qgw=
@@ -496,8 +498,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -568,7 +570,6 @@ golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR
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-20191204190536-9bdfabe68543/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=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=

View File

@@ -9,10 +9,11 @@ import (
type FixedSplit struct {
widget.BaseWidget
Offset float64
Horizontal bool
Leading fyne.CanvasObject
Trailing fyne.CanvasObject
Offset float64
Horizontal bool
SeparatorThickness float32
Leading fyne.CanvasObject
Trailing fyne.CanvasObject
}
func NewFixedHSplitContainer(leading, trailing fyne.CanvasObject, offset float64) *FixedSplit {
@@ -26,10 +27,11 @@ func NewFixedVSplitContainer(top, bottom fyne.CanvasObject, offset float64) *Fix
func NewFixedSplitContainer(leading, trailing fyne.CanvasObject, horizontal bool, offset float64) *FixedSplit {
s := &FixedSplit{
Offset: offset, // Sensible default, can be overridden with SetOffset
Horizontal: horizontal,
Leading: leading,
Trailing: trailing,
Offset: offset, // Sensible default, can be overridden with SetOffset
SeparatorThickness: theme.SeparatorThicknessSize(),
Horizontal: horizontal,
Leading: leading,
Trailing: trailing,
}
s.BaseWidget.ExtendBaseWidget(s)
return s
@@ -53,6 +55,14 @@ func (s *FixedSplit) SetOffset(offset float64) {
s.Refresh()
}
func (s *FixedSplit) SetSepThickness(thickness float32) {
if s.SeparatorThickness == thickness {
return
}
s.SeparatorThickness = thickness
s.Refresh()
}
type fixedSplitContainerRenderer struct {
split *FixedSplit
divider *widget.Separator
@@ -72,7 +82,7 @@ func (r *fixedSplitContainerRenderer) Layout(size fyne.Size) {
leadingSize.Width = lw
leadingSize.Height = size.Height
dividerPos.X = lw
dividerSize.Width = theme.SeparatorThicknessSize()
dividerSize.Width = r.split.SeparatorThickness
dividerSize.Height = size.Height
trailingPos.X = lw + dividerSize.Width
trailingSize.Width = tw
@@ -84,7 +94,7 @@ func (r *fixedSplitContainerRenderer) Layout(size fyne.Size) {
leadingSize.Height = lh
dividerPos.Y = lh
dividerSize.Width = size.Width
dividerSize.Height = theme.SeparatorThicknessSize()
dividerSize.Height = r.split.SeparatorThickness
trailingPos.Y = lh + dividerSize.Height
trailingSize.Width = size.Width
trailingSize.Height = th
@@ -129,7 +139,7 @@ func (r *fixedSplitContainerRenderer) Refresh() {
}
func (r *fixedSplitContainerRenderer) computeSplitLengths(total, lMin, tMin float32) (float32, float32) {
available := float64(total - theme.SeparatorThicknessSize())
available := float64(total - r.split.SeparatorThickness)
if available <= 0 {
return 0, 0
}

View File

@@ -49,7 +49,7 @@ func createHistoryList() fyne.CanvasObject {
btns := object.(*fyne.Container).Objects[2].(*fyne.Container).Objects
m.User = controller.HistoryUser
btns[0].(*widget.Button).OnTapped = func() {
controller.Instance.PlayControl().Play(m)
showDialogIfError(controller.Instance.PlayControl().Play(m))
}
btns[1].(*widget.Button).OnTapped = func() {
controller.Instance.Playlists().GetCurrent().Push(m)

View File

@@ -30,7 +30,7 @@ func createRoomSelector() fyne.CanvasObject {
return controller.Instance.LiveRooms().Size()
},
func() fyne.CanvasObject {
return widget.NewLabel("")
return widget.NewLabel("AAAAAAAAAAAAAAAA")
},
func(id widget.ListItemID, object fyne.CanvasObject) {
object.(*widget.Label).SetText(

View File

@@ -9,6 +9,7 @@ import (
"AynaLivePlayer/gui/gutil"
"AynaLivePlayer/model"
"AynaLivePlayer/resource"
"context"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
@@ -21,6 +22,7 @@ type PlayControllerContainer struct {
Artist *widget.Label
Username *widget.Label
Cover *canvas.Image
coverLoader context.CancelFunc
ButtonPrev *widget.Button
ButtonSwitch *widget.Button
ButtonNext *widget.Button
@@ -170,15 +172,33 @@ func registerPlayControllerHandler() {
if !media.Cover.Exists() {
PlayController.SetDefaultCover()
} else {
if PlayController.coverLoader != nil {
PlayController.coverLoader()
}
var ctx context.Context
ctx, PlayController.coverLoader = context.WithCancel(context.Background())
go func() {
picture, err := gutil.NewImageFromPlayerPicture(media.Cover)
if err != nil {
l().Warn("fail to load parse cover url", media.Cover)
PlayController.SetDefaultCover()
ch := make(chan *canvas.Image)
go func() {
picture, err := gutil.NewImageFromPlayerPicture(media.Cover)
if err != nil {
ch <- nil
return
}
ch <- picture
}()
select {
case <-ctx.Done():
return
case pic := <-ch:
if pic == nil {
PlayController.SetDefaultCover()
return
}
PlayController.Cover.Resource = pic.Resource
PlayController.Cover.Refresh()
}
PlayController.Cover.Resource = picture.Resource
PlayController.Cover.Refresh()
}()
}
})
@@ -217,11 +237,16 @@ func createPlayControllerV2() fyne.CanvasObject {
PlayController.Progress)
PlayController.Title = widget.NewLabel("Title")
PlayController.Title.Wrapping = fyne.TextTruncate
PlayController.Artist = widget.NewLabel("Artist")
PlayController.Username = widget.NewLabel("Username")
titleUser := component.NewFixedHSplitContainer(
PlayController.Title, PlayController.Artist, 0.32)
titleUser.SetSepThickness(0)
playInfo := container.NewBorder(nil, nil, nil, PlayController.Username,
container.NewHBox(PlayController.Title, PlayController.Artist))
titleUser)
registerPlayControllerHandler()

View File

@@ -12,11 +12,13 @@ import (
func createLyricObj(lyric *model.Lyric) []fyne.CanvasObject {
lrcs := make([]fyne.CanvasObject, len(lyric.Lyrics))
for i := 0; i < len(lrcs); i++ {
l := widget.NewLabelWithStyle(
lr := widget.NewLabelWithStyle(
lyric.Lyrics[i].Lyric,
fyne.TextAlignCenter, fyne.TextStyle{Italic: true})
l.Wrapping = fyne.TextWrapWord
lrcs[i] = l
//lr.Wrapping = fyne.TextWrapWord
// todo fix fyne bug
lr.Wrapping = fyne.TextWrapBreak
lrcs[i] = lr
}
return lrcs
}
@@ -41,6 +43,10 @@ func createLyricWindow() fyne.Window {
controller.Instance.PlayControl().GetLyric().EventManager().RegisterA(
model.EventLyricUpdate, "player.lyric.current_lyric", func(event *event.Event) {
e := event.Data.(model.LyricUpdateEvent)
if prevIndex >= len(fullLrc.Objects) || e.Lyric.Index >= len(fullLrc.Objects) {
// fix race condition
return
}
if e.Lyric == nil {
currentLrc.SetText("")
return

View File

@@ -137,7 +137,7 @@ func createPlaylistMedias() fyne.CanvasObject {
btns := object.(*fyne.Container).Objects[2].(*fyne.Container).Objects
m.User = controller.SystemUser
btns[0].(*widget.Button).OnTapped = func() {
controller.Instance.PlayControl().Play(m)
showDialogIfError(controller.Instance.PlayControl().Play(m))
}
btns[1].(*widget.Button).OnTapped = func() {
controller.Instance.Playlists().GetCurrent().Push(m)

View File

@@ -45,7 +45,7 @@ func createSearchList() fyne.CanvasObject {
object.(*fyne.Container).Objects[1].(*widget.Label).SetText(fmt.Sprintf("%d", id))
btns := object.(*fyne.Container).Objects[2].(*fyne.Container).Objects
btns[0].(*widget.Button).OnTapped = func() {
controller.Instance.PlayControl().Play(SearchResult.Items[id])
showDialogIfError(controller.Instance.PlayControl().Play(SearchResult.Items[id]))
}
btns[1].(*widget.Button).OnTapped = func() {
controller.Instance.Playlists().GetCurrent().Push(SearchResult.Items[id])

View File

@@ -13,7 +13,6 @@ var _ fyne.Theme = (*myTheme)(nil)
// return bundled font resource
func (*myTheme) Font(s fyne.TextStyle) fyne.Resource {
l().Debugf("12313123")
if s.Monospace {
return resource.FontMSYaHei
}

View File

@@ -6,6 +6,7 @@ import (
const (
EventPlay event.EventId = "player.play"
EventPlayed event.EventId = "player.played"
EventPlaylistPreInsert event.EventId = "playlist.insert.pre"
EventPlaylistInsert event.EventId = "playlist.insert.after"
EventPlaylistUpdate event.EventId = "playlist.update"

View File

@@ -32,6 +32,9 @@ func LoadLyric(lyric string) *Lyric {
times := make([]float64, 0)
for _, line := range strings.Split(lyric, "\n") {
lrc := timeTagRegex.ReplaceAllString(line, "")
if len(lrc) > 0 && lrc[len(lrc)-1] == '\r' {
lrc = lrc[:len(lrc)-1]
}
for _, time := range timeTagRegex.FindAllString(line, -1) {
ts := strings.Split(time[1:len(time)-1], ":")
t := cast.ToFloat64(ts[0])*60 + cast.ToFloat64(ts[1])

View File

@@ -61,7 +61,7 @@ func init() {
func (b *BilibiliVideo) getPage(bv string) int {
if page := b.PageRegex.FindString(bv); page != "" {
return util.StringToInt(page[2:])
return util.Atoi(page[2:])
}
return 0
}

View File

@@ -71,7 +71,7 @@ func TestKuwo_UpdateMediaLyric(t *testing.T) {
func TestKuwo_GetPlaylist(t *testing.T) {
var api MediaProvider = KuwoAPI
playlist, err := api.GetPlaylist(model.Meta{
playlist, err := api.GetPlaylist(&model.Meta{
Name: api.GetName(),
//Id: "1082685104",
Id: "2959147566",

View File

@@ -1,6 +1,7 @@
package provider
import (
"AynaLivePlayer/common/util"
"AynaLivePlayer/model"
"os"
"sort"
@@ -90,6 +91,22 @@ func (l *Local) GetPlaylist(playlist *model.Meta) ([]*model.Media, error) {
}
func (l *Local) Search(keyword string) ([]*model.Media, error) {
allMedias := make([]*model.Media, 0)
for _, p := range l.Playlists {
for _, m := range p.Medias {
allMedias = append(allMedias, m)
}
}
MediaSort(keyword, allMedias)
c := util.Min(len(allMedias), 32)
medias := make([]*model.Media, c)
for i := 0; i < c; i++ {
medias[i] = allMedias[i].Copy()
}
return medias, nil
}
func (l *Local) SearchV1(keyword string) ([]*model.Media, error) {
result := make([]struct {
M *model.Media
N int

View File

@@ -114,7 +114,7 @@ func (n *Netease) FormatPlaylistUrl(uri string) string {
func (n *Netease) GetPlaylist(playlist *model.Meta) ([]*model.Media, error) {
result, err := neteaseApi.GetPlaylistDetail(
n.ReqData, util.StringToInt(playlist.Id))
n.ReqData, util.Atoi(playlist.Id))
if err != nil || result.Code != 200 {
return nil, ErrorExternalApi
}
@@ -197,7 +197,7 @@ func (n *Netease) Search(keyword string) ([]*model.Media, error) {
func (n *Netease) UpdateMedia(media *model.Media) error {
result, err := neteaseApi.GetSongDetail(
n.ReqData,
[]int{util.StringToInt(media.Meta.(model.Meta).Id)})
[]int{util.Atoi(media.Meta.(model.Meta).Id)})
if err != nil || result.Code != 200 {
return ErrorExternalApi
}
@@ -214,7 +214,7 @@ func (n *Netease) UpdateMedia(media *model.Media) error {
func (n *Netease) UpdateMediaUrl(media *model.Media) error {
result, err := neteaseApi.GetSongURL(
n.ReqData,
neteaseApi.SongURLConfig{Ids: []int{util.StringToInt(media.Meta.(model.Meta).Id)}})
neteaseApi.SongURLConfig{Ids: []int{util.Atoi(media.Meta.(model.Meta).Id)}})
if err != nil || result.Code != 200 {
return ErrorExternalApi
}
@@ -229,7 +229,7 @@ func (n *Netease) UpdateMediaUrl(media *model.Media) error {
}
func (n *Netease) UpdateMediaLyric(media *model.Media) error {
result, err := neteaseApi.GetSongLyric(n.ReqData, util.StringToInt(media.Meta.(model.Meta).Id))
result, err := neteaseApi.GetSongLyric(n.ReqData, util.Atoi(media.Meta.(model.Meta).Id))
if err != nil || result.Code != 200 {
return ErrorExternalApi
}

View File

@@ -20,6 +20,20 @@ func TestNetease_Search(t *testing.T) {
fmt.Println(media.Url)
}
func TestNetease_Search2(t *testing.T) {
var api MediaProvider = NeteaseAPI
result, err := api.Search("出山")
if err != nil {
return
}
t.Log(result)
media := result[0]
t.Log(media)
err = api.UpdateMediaUrl(media)
t.Log(err)
t.Log(media.Url)
}
func TestNetease_GetMusicMeta(t *testing.T) {
var api MediaProvider = NeteaseAPI
@@ -59,7 +73,7 @@ func TestNetease_GetMusic(t *testing.T) {
func TestNetease_GetPlaylist(t *testing.T) {
var api MediaProvider = NeteaseAPI
playlist, err := api.GetPlaylist(model.Meta{
playlist, err := api.GetPlaylist(&model.Meta{
Name: api.GetName(),
//Id: "2520739691",
Id: "2382819181",

26
repo/provider/util.go Normal file
View File

@@ -0,0 +1,26 @@
package provider
import (
"AynaLivePlayer/common/util"
"AynaLivePlayer/model"
"sort"
)
func MediaSort(keyword string, medias []*model.Media) {
mediaDist := make([]struct {
media *model.Media
dist int
}, len(medias))
for i, media := range medias {
mediaDist[i].media = media
mediaDist[i].dist = util.StrLen(util.LongestCommonString(keyword, media.Title)) +
util.StrLen(util.LongestCommonString(keyword, media.Artist))
}
sort.Slice(mediaDist, func(i, j int) bool {
return mediaDist[i].dist > mediaDist[j].dist
})
for i, media := range mediaDist {
medias[i] = media.media
}
return
}

File diff suppressed because one or more lines are too long

View File

@@ -13,23 +13,25 @@ beta
----
Finished
- 2022.12.24 : 修复拖动进度条时产生的噪音
- 2022.12.23 : 重写controller部分修改search界面添加歌词滚动效果部分资源添加到bundle
- 2022.8.24 : 本地搜索优化/牌子点歌
- 2022.8.19@0.9.2: 修复歌词不更新/修复web自定义页面空白
- 2022.7.21 : 修复本地歌单的bug/fix webinfo can't apply media-cover css/修复切换歌单时不自动刷新
- 2022.7.18@0.9.0: Fix bug/网页第二版跟新,加入自定义模板/修复图片加载不出来导致的闪退bug
- 2022.7.18 : Fix bug
- 2022.7.16@0.8.6: 网页输出第一版更新/修复历史列表部分歌曲放不出来的bug/修复部分歌词不更新
- 2022.7.15: 更新stderr重定向/添加logo/
- 2022.7.13@0.8.4: 网易云登录
- 2022.7.10: Local Provider
- 2022.7.03: 多来源点歌
- 2022.7.01: 文本输出
- 2022.7.01: 历史记录
- 2022.6.29: 跳过闲置歌单
- 2022.6.26: i18n
- 2022.6.25: kuwo歌单
- 2022.6.25: 设置界面
- 2022.6.25: @6 bug, race condition, playlist size changed during playlist update.
- 2022.6.23: 用户歌单操作
- 2022.12.24@0.9.5: ui界面优化event handler优化-新增任务池模式,歌词加载优化,新房间管理(可以自动连接)
本地音频搜索算法优化,
- 2022.12.24 : 修复拖动进度条时产生的噪音
- 2022.12.23 : 重写controller部分修改search界面添加歌词滚动效果部分资源添加到bundle
- 2022.8.24 : 本地搜索优化/牌子点歌
- 2022.8.19@0.9.2 : 修复歌词不更新/修复web自定义页面空白
- 2022.7.21 : 修复本地歌单的bug/fix webinfo can't apply media-cover css/修复切换歌单时不自动刷新
- 2022.7.18@0.9.0 : Fix bug/网页第二版跟新,加入自定义模板/修复图片加载不出来导致的闪退bug
- 2022.7.18 : Fix bug
- 2022.7.16@0.8.6 : 网页输出第一版更新/修复历史列表部分歌曲放不出来的bug/修复部分歌词不更新
- 2022.7.15: 更新stderr重定向/添加logo/
- 2022.7.13@0.8.4 : 网易云登录
- 2022.7.10: Local Provider
- 2022.7.03: 多来源点歌
- 2022.7.01: 文本输出
- 2022.7.01: 历史记录
- 2022.6.29: 跳过闲置歌单
- 2022.6.26: i18n
- 2022.6.25: kuwo歌单
- 2022.6.25: 设置界面
- 2022.6.25: @6 bug, race condition, playlist size changed during playlist update.
- 2022.6.23: 用户歌单操作

View File

@@ -1,209 +0,0 @@
# IDK what to write
后端已经写好了!!!
只需要负责前端的部分就行了。
开源地址: [https://github.com/aynakeya/AynaLivePlayer](https://github.com/aynakeya/AynaLivePlayer)
# 要求
- 开源项目.jpg
- 给OBS用的用来显示信息
- 访问的时候先获取全部的数据渲染然后用websocket获取更新的数据
- 能够单个输出所有的数据 @1
- 能自定义输出的模板(?暂定) @2
- 最好用vue
### @1 单个输出所有的数据
类似[https://github.com/aynakeya/BiliAudioBot](https://github.com/aynakeya/BiliAudioBot)里的frontend(用vue写的)可以单个输出所有的数据。release有打包好发布的测试用例。
- 适合简单的输出
- 得有一个css的class这样子用户可以自己在obs自定义css虽然大部分用户不知道怎么搞
- 如果个可以的话最好能有个页面能可视化的修改css
比如GET `/info/CurrentTitle` 或者 `info?target=Current.Title` 返回
```
<p class=".current-title">外婆桥</p>
```
比如 GET `info?target=Current.Cover` 返回
```
<img class=".current-title" src="..." />
```
比如 GET `info?target=Playlist` 返回
```
<ol>
<li v-for="item in playlist">
<h2 class="playlist-info">#{{ playlist.indexOf(item) }} - {{ item.title }} - {{ item.artist }} - {{ item.username }}</h2>
</li>
</ol>
```
### @2 自定义输出的模板
- **第一目标** 不整了/没想好怎么整
- **第二目标** 最简单的还是和TextInfo一样用户给一个模板(通过get?会不会太长了,或者本地读取?这样的话我后端获取还得加一个api来返回模板) 然后渲染出对应的html的代码当然也要有css这样子用户可以自定义
```
Title: {{ .Current.Title }}
Artist: {{ .Current.Artist }}
Album: {{ .Current.Album}}
Username: {{ .Current.Username }}
Progress(in seconds): {{.CurrentTime}} / {{.TotalTime}}
Progress(in minutes:seconds): {{ GetMinutes .CurrentTime}}:{{ GetSeconds .CurrentTime}} / {{ GetMinutes .TotalTime}}:{{ GetSeconds .TotalTime}}
Lyric: {{.Lyric}}
{{range .Playlist}}
Index: # {{ .Index}}
Title: {{ .Title }}
Artist: {{ .Artist }}
Album: {{ .Album}}
Username: {{ .Username }}
{{end}}
```
- **终极目标**不知道能不能想做的像[https://github.com/xfgryujk/blivechat](https://github.com/xfgryujk/blivechat) / [https://link.bilibili.com/ctool/vtuber/index.html](https://link.bilibili.com/ctool/vtuber/index.html), 但是感觉挺麻烦的
# 后端接口
前端的页面在/地址下
两个接口
```
mux.Handle("/", http.FileServer(http.Dir(config.GetAssetPath("webinfo")))) # ./assets/webinfo
mux.HandleFunc("/ws/info", server.handleInfo)
mux.HandleFunc("/api/info", server.getInfo)
```
## api详情
### GET /api/info
**http://127.0.0.1:4000/api/info**
返回一个`OutInfo`, 当前的所有数据
```
{
"Current": {
"Index": 0,
"Title": "外婆桥",
"Artist": "任然",
"Album": "外婆桥",
"Username": "System",
"Cover": {
"Url": "https://p1.music.126.net/Ep-CjAsRL5yvZkDreiWsMQ==/109951164390004861.jpg",
"Data": null
}
},
"CurrentTime": 6,
"TotalTime": 261,
"Lyric": " 编曲 : 闫津",
"Playlist": [
{
"Index": 0,
"Title": "Melody",
"Artist": "ZIV,KIPES",
"Album": "倒叙爱情",
"Username": "System",
"Cover": {
"Url": "",
"Data": null
}
},
{
"Index": 1,
"Title": "Cure For Me",
"Artist": "AURORA",
"Album": "Cure For Me",
"Username": "System",
"Cover": {
"Url": "",
"Data": null
}
},
{
"Index": 2,
"Title": "填满",
"Artist": "苏星婕",
"Album": "填满",
"Username": "System",
"Cover": {
"Url": "",
"Data": null
}
}
]
}
```
### websocket /ws/info
**ws://127.0.0.1:4000/ws/info**
返回一个`WebsocketData`, 更新的数据
```
{
"Update": "Lyric",
"Data": {
"Current": {
"Index": 0,
"Title": "",
"Artist": "",
"Album": "",
"Username": "",
"Cover": {
"Url": "",
"Data": null
}
},
"CurrentTime": 0,
"TotalTime": 0,
"Lyric": " 混音 : KIPES",
"Playlist": null
}
}
```
## Structures
```
# Url == "" && Data == nil 或者Url != "" || Data != nil
type Picture struct {
Url string # 如果是url就会给url
Data []byte # 如果是二进制数据就是base64
}
type MediaInfo struct {
Index int
Title string
Artist string
Album string
Username string
Cover player.Picture
}
type OutInfo struct {
Current MediaInfo
CurrentTime int
TotalTime int
Lyric string
Playlist []MediaInfo
}
type WebsocketData struct {
Update string
Data OutInfo
}
```