mirror of
https://github.com/AynaLivePlayer/AynaLivePlayer.git
synced 2025-12-06 10:22:50 +08:00
ui界面优化,event handler优化-新增任务池模式,歌词加载优化,新房间管理(可以自动连接) 本地音频搜索算法优化,
This commit is contained in:
16
Makefile
16
Makefile
@@ -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
27
app/wrapwordbug/main.go
Normal 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()
|
||||
}
|
||||
1
assets/scripts/windows/强制结束-出现任何奇怪问题点我重置.bat
Normal file
1
assets/scripts/windows/强制结束-出现任何奇怪问题点我重置.bat
Normal file
@@ -0,0 +1 @@
|
||||
taskkill /IM "AynaLivePlayer.exe" /F
|
||||
1
assets/scripts/windows/重置winsock.bat
Normal file
1
assets/scripts/windows/重置winsock.bat
Normal file
@@ -0,0 +1 @@
|
||||
netsh winsock reset
|
||||
69
common/util/generic.go
Normal file
69
common/util/generic.go
Normal 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
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package util
|
||||
|
||||
import "image"
|
||||
|
||||
func ReadImage(path string) image.Image {
|
||||
return nil
|
||||
}
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
32
common/util/string_test.go
Normal file
32
common/util/string_test.go
Normal 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("布拉格广场 周杰伦", "布拉格广场"))
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
9
go.mod
@@ -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
13
go.sum
@@ -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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
26
repo/provider/util.go
Normal 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
42
todo.txt
42
todo.txt
@@ -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: 用户歌单操作
|
||||
209
前端要求.md
209
前端要求.md
@@ -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
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user