mirror of
https://github.com/AynaLivePlayer/AynaLivePlayer.git
synced 2025-12-06 10:22:50 +08:00
rewrite
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,3 +1,7 @@
|
||||
.idea
|
||||
assets/webinfo/*.html
|
||||
assets/webinfo/assets
|
||||
assets/webinfo/assets
|
||||
music
|
||||
liverooms.json
|
||||
playlists.json
|
||||
textinfo
|
||||
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
[submodule "pkg/miaosic"]
|
||||
path = pkg/miaosic
|
||||
url = git@github.com:AynaLivePlayer/miaosic.git
|
||||
[submodule "pkg/liveroom-sdk"]
|
||||
path = pkg/liveroom-sdk
|
||||
url = git@github.com:AynaLivePlayer/liveroom-sdk.git
|
||||
@@ -1,24 +0,0 @@
|
||||
package adapters
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/adapters/liveclient"
|
||||
"AynaLivePlayer/adapters/logger"
|
||||
"AynaLivePlayer/adapters/player"
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/core/adapter"
|
||||
)
|
||||
|
||||
var Logger = &logger.LoggerFactory{}
|
||||
|
||||
var LiveClient = &liveclient.LiveClientFactory{
|
||||
LiveClients: map[string]adapter.LiveClientCtor{
|
||||
"bilibili": liveclient.BilibiliCtor,
|
||||
},
|
||||
EventManager: event.MainManager,
|
||||
Logger: &logger.EmptyLogger{},
|
||||
}
|
||||
|
||||
var Player = &player.PlayerFactory{
|
||||
EventManager: event.MainManager,
|
||||
Logger: &logger.EmptyLogger{},
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
package liveclient
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/core/adapter"
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/aynakeya/blivedm"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/tidwall/gjson"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Bilibili struct {
|
||||
client *blivedm.BLiveWsClient
|
||||
eventManager *event.Manager
|
||||
roomName string
|
||||
status bool
|
||||
log adapter.ILogger
|
||||
}
|
||||
|
||||
func BilibiliCtor(id string, em *event.Manager, log adapter.ILogger) (adapter.LiveClient, error) {
|
||||
room, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
return nil, errors.New("room id for bilibili should be a integer")
|
||||
}
|
||||
return NewBilibili(room, em, log), nil
|
||||
}
|
||||
|
||||
func NewBilibili(roomId int, em *event.Manager, log adapter.ILogger) adapter.LiveClient {
|
||||
cl := &Bilibili{
|
||||
client: &blivedm.BLiveWsClient{ShortId: roomId, Account: blivedm.DanmuAccount{UID: 0}, HearbeatInterval: 10 * time.Second},
|
||||
eventManager: em,
|
||||
roomName: "",
|
||||
log: log,
|
||||
}
|
||||
cl.client.OnDisconnect = func(client *blivedm.BLiveWsClient) {
|
||||
cl.log.Warn("[Bilibili LiveChatSDK] disconnect from websocket connection, maybe try reconnect")
|
||||
cl.status = false
|
||||
cl.eventManager.CallA(events.LiveRoomStatusChange, events.StatusChangeEvent{Connected: false, Client: cl})
|
||||
}
|
||||
cl.client.RegHandler(blivedm.CmdDanmaku, cl.handleMsg)
|
||||
return cl
|
||||
}
|
||||
|
||||
func (b *Bilibili) ClientName() string {
|
||||
return "bilibili"
|
||||
}
|
||||
|
||||
func (b *Bilibili) RoomName() string {
|
||||
return b.roomName
|
||||
}
|
||||
|
||||
func (b *Bilibili) Status() bool {
|
||||
return b.status
|
||||
}
|
||||
|
||||
func (b *Bilibili) EventManager() *event.Manager {
|
||||
return b.eventManager
|
||||
}
|
||||
|
||||
func (b *Bilibili) Connect() bool {
|
||||
if b.status {
|
||||
return true
|
||||
}
|
||||
b.log.Info("[Bilibili LiveChatSDK] Trying Connect Danmu Server")
|
||||
if !b.client.GetRoomInfo() {
|
||||
b.log.Info("[Bilibili LiveChatSDK] Connect Failed")
|
||||
return false
|
||||
}
|
||||
resp, err := resty.New().R().
|
||||
SetQueryParam("room_id", strconv.Itoa(b.client.RoomId)).
|
||||
Get("https://scene.aynakeya.com:3000/bilisrv/dminfo")
|
||||
if err != nil {
|
||||
b.log.Info("[Bilibili LiveChatSDK] Connect Failed")
|
||||
return false
|
||||
}
|
||||
gjresult := gjson.Parse(resp.String())
|
||||
if gjresult.Get("code").Int() != 0 {
|
||||
b.log.Info("[Bilibili LiveChatSDK] Connect Failed")
|
||||
return false
|
||||
}
|
||||
b.client.DanmuInfo = blivedm.DanmuInfoData{
|
||||
Token: gjresult.Get("data.token").String(),
|
||||
}
|
||||
b.client.Account.UID = int(gjresult.Get("data.uid").Int())
|
||||
if err := json.Unmarshal([]byte(gjresult.Get("data.host_list").String()), &b.client.DanmuInfo.HostList); err != nil {
|
||||
return false
|
||||
}
|
||||
if b.client.ConnectDanmuServer() {
|
||||
b.roomName = b.client.RoomInfo.Title
|
||||
b.status = true
|
||||
b.eventManager.CallA(events.LiveRoomStatusChange, events.StatusChangeEvent{Connected: true, Client: b})
|
||||
b.log.Info("[Bilibili LiveChatSDK] Connect Success")
|
||||
return true
|
||||
}
|
||||
b.log.Info("[Bilibili LiveChatSDK] Connect Failed")
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *Bilibili) Disconnect() bool {
|
||||
b.log.Info("[Bilibili LiveChatSDK] Disconnect from danmu server")
|
||||
if b.client == nil {
|
||||
return true
|
||||
}
|
||||
b.client.Disconnect()
|
||||
b.eventManager.CallA(events.LiveRoomStatusChange, events.StatusChangeEvent{Connected: false, Client: b})
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *Bilibili) handleMsg(context *blivedm.Context) {
|
||||
msg, ok := context.ToDanmakuMessage()
|
||||
if !ok {
|
||||
b.log.Warn("[Bilibili LiveChatSDK] handle message failed, can't convert context to danmu message")
|
||||
return
|
||||
}
|
||||
dmsg := model.DanmuMessage{
|
||||
User: model.DanmuUser{
|
||||
Uid: strconv.FormatInt(msg.Uid, 10),
|
||||
Username: msg.Uname,
|
||||
Medal: model.UserMedal{
|
||||
Name: msg.MedalName,
|
||||
Level: int(msg.MedalLevel),
|
||||
},
|
||||
Admin: msg.Admin,
|
||||
Privilege: int(msg.PrivilegeType),
|
||||
},
|
||||
Message: msg.Msg,
|
||||
}
|
||||
b.log.Debug("[Bilibili LiveChatSDK] receive message", dmsg)
|
||||
go func() {
|
||||
b.eventManager.Call(&event.Event{
|
||||
Id: events.LiveRoomMessageReceive,
|
||||
Cancelled: false,
|
||||
Data: &dmsg,
|
||||
})
|
||||
}()
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package liveclient
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/core/adapter"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type LiveClientFactory struct {
|
||||
LiveClients map[string]adapter.LiveClientCtor
|
||||
EventManager *event.Manager
|
||||
Logger adapter.ILogger
|
||||
}
|
||||
|
||||
func (f *LiveClientFactory) GetAllClientNames() []string {
|
||||
names := make([]string, 0)
|
||||
for key, _ := range f.LiveClients {
|
||||
names = append(names, key)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func (f *LiveClientFactory) NewLiveClient(clientName, id string) (adapter.LiveClient, error) {
|
||||
ctor, ok := f.LiveClients[clientName]
|
||||
if !ok {
|
||||
return nil, errors.New("no such client")
|
||||
}
|
||||
return ctor(id, f.EventManager.NewChildManager(), f.Logger)
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/adapter"
|
||||
)
|
||||
|
||||
type EmptyLogger struct {
|
||||
}
|
||||
|
||||
func (e EmptyLogger) Debug(args ...interface{}) {
|
||||
return
|
||||
}
|
||||
|
||||
func (e EmptyLogger) Debugf(format string, args ...interface{}) {
|
||||
return
|
||||
}
|
||||
|
||||
func (e EmptyLogger) Info(args ...interface{}) {
|
||||
return
|
||||
}
|
||||
|
||||
func (e EmptyLogger) Infof(format string, args ...interface{}) {
|
||||
return
|
||||
}
|
||||
|
||||
func (e EmptyLogger) Warn(args ...interface{}) {
|
||||
return
|
||||
}
|
||||
|
||||
func (e EmptyLogger) Warnf(format string, args ...interface{}) {
|
||||
return
|
||||
}
|
||||
|
||||
func (e EmptyLogger) Error(args ...interface{}) {
|
||||
return
|
||||
}
|
||||
|
||||
func (e EmptyLogger) Errorf(format string, args ...interface{}) {
|
||||
return
|
||||
}
|
||||
|
||||
func (e EmptyLogger) WithModule(prefix string) adapter.ILogger {
|
||||
return &EmptyLogger{}
|
||||
}
|
||||
|
||||
func (e EmptyLogger) SetLogLevel(level adapter.LogLevel) {
|
||||
return
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/adapter"
|
||||
)
|
||||
|
||||
type LoggerFactory struct {
|
||||
LiveClients map[string]adapter.LiveClientCtor
|
||||
}
|
||||
|
||||
func (f *LoggerFactory) NewLogrus(filename string, redirectStderr bool, maxSize int64) adapter.ILogger {
|
||||
return NewLogrusLogger(filename, redirectStderr, maxSize)
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/adapter"
|
||||
nested "github.com/antonfisher/nested-logrus-formatter"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/virtuald/go-paniclog"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type LogrusLogger struct {
|
||||
*logrus.Entry
|
||||
module string
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) SetLogLevel(level adapter.LogLevel) {
|
||||
switch level {
|
||||
case adapter.LogLevelDebug:
|
||||
l.Logger.SetLevel(logrus.DebugLevel)
|
||||
case adapter.LogLevelInfo:
|
||||
l.Logger.SetLevel(logrus.InfoLevel)
|
||||
case adapter.LogLevelWarn:
|
||||
l.Logger.SetLevel(logrus.WarnLevel)
|
||||
case adapter.LogLevelError:
|
||||
l.Logger.SetLevel(logrus.ErrorLevel)
|
||||
default:
|
||||
l.Logger.SetLevel(logrus.InfoLevel)
|
||||
}
|
||||
}
|
||||
|
||||
func NewLogrusLogger(fileName string, redirectStderr bool, maxSize int64) *LogrusLogger {
|
||||
l := logrus.New()
|
||||
l.SetFormatter(
|
||||
&nested.Formatter{
|
||||
FieldsOrder: []string{"Module"},
|
||||
HideKeys: true,
|
||||
NoColors: true,
|
||||
})
|
||||
var file *os.File
|
||||
var err error
|
||||
if fileName != "" {
|
||||
fi, err := os.Stat(fileName)
|
||||
if err != nil {
|
||||
file, err = os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY, 0666)
|
||||
} else if fi.Size() > maxSize*1024*1024 {
|
||||
file, err = os.OpenFile(fileName, os.O_TRUNC|os.O_WRONLY, 0666)
|
||||
} else {
|
||||
file, err = os.OpenFile(fileName, os.O_WRONLY|os.O_APPEND, 0666)
|
||||
}
|
||||
if err == nil {
|
||||
l.Out = io.MultiWriter(file, os.Stdout)
|
||||
} else {
|
||||
l.Info("Failed to log to file, using default stdout")
|
||||
}
|
||||
}
|
||||
if redirectStderr && file != nil {
|
||||
l.Info("panic/stderr redirect to log file")
|
||||
if _, err = paniclog.RedirectStderr(file); err != nil {
|
||||
l.Infof("Failed to redirect stderr to to file: %s", err)
|
||||
}
|
||||
}
|
||||
return &LogrusLogger{
|
||||
Entry: logrus.NewEntry(l),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LogrusLogger) WithModule(prefix string) adapter.ILogger {
|
||||
return &LogrusLogger{
|
||||
Entry: l.Entry.WithField("Module", prefix),
|
||||
module: prefix,
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package player
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/core/adapter"
|
||||
)
|
||||
|
||||
type PlayerFactory struct {
|
||||
EventManager *event.Manager
|
||||
Logger adapter.ILogger
|
||||
}
|
||||
|
||||
func (f *PlayerFactory) NewMPV() adapter.IPlayer {
|
||||
return NewMpvPlayer(f.EventManager, f.Logger)
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
package player
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/common/util"
|
||||
"AynaLivePlayer/core/adapter"
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"fmt"
|
||||
"github.com/aynakeya/go-mpv"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
var mpvPropertyMap = map[model.PlayerProperty]string{
|
||||
model.PlayerPropDuration: "duration",
|
||||
model.PlayerPropTimePos: "time-pos",
|
||||
model.PlayerPropIdleActive: "idle-active",
|
||||
model.PlayerPropPercentPos: "percent-pos",
|
||||
model.PlayerPropPause: "pause",
|
||||
model.PlayerPropVolume: "volume",
|
||||
}
|
||||
|
||||
var mpvPropertyMapInv = map[string]model.PlayerProperty{
|
||||
"duration": model.PlayerPropDuration,
|
||||
"time-pos": model.PlayerPropTimePos,
|
||||
"idle-active": model.PlayerPropIdleActive,
|
||||
"percent-pos": model.PlayerPropPercentPos,
|
||||
"pause": model.PlayerPropPause,
|
||||
"volume": model.PlayerPropVolume,
|
||||
}
|
||||
|
||||
type MpvPlayer struct {
|
||||
running bool
|
||||
libmpv *mpv.Mpv
|
||||
Playing *model.Media
|
||||
propertyWatchedFlag map[model.PlayerProperty]int
|
||||
eventManager *event.Manager
|
||||
log adapter.ILogger
|
||||
}
|
||||
|
||||
func NewMpvPlayer(em *event.Manager, log adapter.ILogger) adapter.IPlayer {
|
||||
player := &MpvPlayer{
|
||||
running: true,
|
||||
libmpv: mpv.Create(),
|
||||
propertyWatchedFlag: make(map[model.PlayerProperty]int),
|
||||
eventManager: em,
|
||||
log: log,
|
||||
}
|
||||
err := player.libmpv.Initialize()
|
||||
if err != nil {
|
||||
player.log.Error("[MPV Player] initialize libmpv failed")
|
||||
return nil
|
||||
}
|
||||
_ = player.libmpv.SetOptionString("vo", "null")
|
||||
player.log.Info("[MPV Player] initialize libmpv success")
|
||||
_ = player.ObserveProperty(model.PlayerPropIdleActive, "player.setplaying", func(evnt *event.Event) {
|
||||
isIdle := evnt.Data.(events.PlayerPropertyUpdateEvent).Value.(bool)
|
||||
if isIdle {
|
||||
player.Playing = nil
|
||||
}
|
||||
})
|
||||
player.Start()
|
||||
return player
|
||||
}
|
||||
|
||||
func (p *MpvPlayer) Start() {
|
||||
p.log.Info("[MPV Player] starting mpv player")
|
||||
go func() {
|
||||
for p.running {
|
||||
e := p.libmpv.WaitEvent(1)
|
||||
if e == nil {
|
||||
p.log.Warn("[MPV Player] event loop got nil event")
|
||||
}
|
||||
//p.log.Trace("[MPV Player] new event", e)
|
||||
if e.EventId == mpv.EVENT_PROPERTY_CHANGE {
|
||||
eventProperty := e.Property()
|
||||
property, ok := mpvPropertyMapInv[eventProperty.Name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
var value interface{} = nil
|
||||
if eventProperty.Data != nil {
|
||||
value = eventProperty.Data.(mpv.Node).Value
|
||||
}
|
||||
p.eventManager.CallA(
|
||||
events.EventPlayerPropertyUpdate(property),
|
||||
events.PlayerPropertyUpdateEvent{
|
||||
Property: property,
|
||||
Value: value,
|
||||
})
|
||||
|
||||
}
|
||||
if e.EventId == mpv.EVENT_SHUTDOWN {
|
||||
p.log.Info("[MPV Player] libmpv shutdown")
|
||||
p.Stop()
|
||||
}
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
func (p *MpvPlayer) Stop() {
|
||||
p.log.Info("[MPV Player] stopping mpv player")
|
||||
p.running = false
|
||||
p.libmpv.TerminateDestroy()
|
||||
}
|
||||
|
||||
func (p *MpvPlayer) GetPlaying() *model.Media {
|
||||
return p.Playing
|
||||
}
|
||||
|
||||
func (p *MpvPlayer) SetWindowHandle(handle uintptr) error {
|
||||
p.log.Infof("[MPV Player] set window handle %d", handle)
|
||||
_ = p.libmpv.SetOptionString("wid", fmt.Sprintf("%d", handle))
|
||||
return p.libmpv.SetOptionString("vo", "gpu")
|
||||
}
|
||||
|
||||
func (p *MpvPlayer) Play(media *model.Media) error {
|
||||
p.log.Infof("[MPV Player] Play media %s", media.Url)
|
||||
if val, ok := media.Header["User-Agent"]; ok {
|
||||
p.log.Debug("[MPV PlayControl] set user-agent for mpv player")
|
||||
err := p.libmpv.SetPropertyString("user-agent", val)
|
||||
if err != nil {
|
||||
p.log.Warn("[MPV PlayControl] set player user-agent failed", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := media.Header["Referer"]; ok {
|
||||
p.log.Debug("[MPV PlayControl] set referrer for mpv player")
|
||||
err := p.libmpv.SetPropertyString("referrer", val)
|
||||
if err != nil {
|
||||
p.log.Warn("[MPV PlayControl] set player referrer failed", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
p.log.Debugf("mpv command load file %s %s", media.Title, media.Url)
|
||||
if err := p.libmpv.Command([]string{"loadfile", media.Url}); err != nil {
|
||||
p.log.Warn("[MPV PlayControl] mpv load media failed", media)
|
||||
return err
|
||||
}
|
||||
p.Playing = media
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *MpvPlayer) IsPaused() bool {
|
||||
property, err := p.libmpv.GetProperty("pause", mpv.FORMAT_FLAG)
|
||||
if err != nil {
|
||||
p.log.Warn("[MPV PlayControl] get property pause failed", err)
|
||||
return false
|
||||
}
|
||||
return property.(bool)
|
||||
}
|
||||
|
||||
func (p *MpvPlayer) Pause() error {
|
||||
p.log.Debugf("[MPV Player] pause")
|
||||
return p.libmpv.SetProperty("pause", mpv.FORMAT_FLAG, true)
|
||||
}
|
||||
|
||||
func (p *MpvPlayer) Unpause() error {
|
||||
p.log.Debugf("[MPV Player] unpause")
|
||||
return p.libmpv.SetProperty("pause", mpv.FORMAT_FLAG, false)
|
||||
}
|
||||
|
||||
// SetVolume set mpv volume, from 0.0 - 100.0
|
||||
func (p *MpvPlayer) SetVolume(volume float64) error {
|
||||
p.log.Debugf("[MPV Player] set volume to %f", volume)
|
||||
return p.libmpv.SetProperty("volume", mpv.FORMAT_DOUBLE, volume)
|
||||
}
|
||||
|
||||
func (p *MpvPlayer) IsIdle() bool {
|
||||
property, err := p.libmpv.GetProperty("idle-active", mpv.FORMAT_FLAG)
|
||||
if err != nil {
|
||||
p.log.Warn("[MPV Player] get property idle-active failed", err)
|
||||
return false
|
||||
}
|
||||
return property.(bool)
|
||||
}
|
||||
|
||||
// Seek change position for current file
|
||||
// absolute = true : position is the time in second
|
||||
// absolute = false: position is in percentage eg 0.1 0.2
|
||||
func (p *MpvPlayer) Seek(position float64, absolute bool) error {
|
||||
p.log.Debugf("[MPV Player] seek to %f (absolute=%t)", position, absolute)
|
||||
if absolute {
|
||||
return p.libmpv.SetProperty("time-pos", mpv.FORMAT_DOUBLE, position)
|
||||
} else {
|
||||
return p.libmpv.SetProperty("percent-pos", mpv.FORMAT_DOUBLE, position)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *MpvPlayer) ObserveProperty(property model.PlayerProperty, name string, handler event.HandlerFunc) error {
|
||||
p.log.Debugf("[MPV Player] add property observer for mpv")
|
||||
p.eventManager.RegisterA(
|
||||
events.EventPlayerPropertyUpdate(property),
|
||||
name, handler)
|
||||
if _, ok := p.propertyWatchedFlag[property]; !ok {
|
||||
p.propertyWatchedFlag[property] = 1
|
||||
return p.libmpv.ObserveProperty(util.Hash64(mpvPropertyMap[property]), mpvPropertyMap[property], mpv.FORMAT_NODE)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAudioDeviceList get output device for mpv
|
||||
// return format is []AudioDevice
|
||||
func (p *MpvPlayer) GetAudioDeviceList() ([]model.AudioDevice, error) {
|
||||
p.log.Debugf("[MPV Player] getting audio device list for mpv")
|
||||
property, err := p.libmpv.GetProperty("audio-device-list", mpv.FORMAT_STRING)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dl := make([]model.AudioDevice, 0)
|
||||
gjson.Parse(property.(string)).ForEach(func(key, value gjson.Result) bool {
|
||||
dl = append(dl, model.AudioDevice{
|
||||
Name: value.Get("name").String(),
|
||||
Description: value.Get("description").String(),
|
||||
})
|
||||
return true
|
||||
})
|
||||
return dl, nil
|
||||
}
|
||||
|
||||
func (p *MpvPlayer) SetAudioDevice(device string) error {
|
||||
p.log.Debugf("[MPV Player] set audio device %s for mpv", device)
|
||||
return p.libmpv.SetPropertyString("audio-device", device)
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package player
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/adapters/logger"
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/core/model"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPlayer(t *testing.T) {
|
||||
player := NewMpvPlayer(event.MainManager, &logger.EmptyLogger{})
|
||||
player.Start()
|
||||
defer player.Stop()
|
||||
|
||||
player.ObserveProperty("time-pos", "testplayer.timepos", func(evnt *event.Event) {
|
||||
fmt.Println(1, evnt.Data)
|
||||
})
|
||||
player.ObserveProperty("percent-pos", "testplayer.percentpos", func(evnt *event.Event) {
|
||||
fmt.Println(2, evnt.Data)
|
||||
})
|
||||
player.Play(&model.Media{
|
||||
Url: "https://ia600809.us.archive.org/19/items/VillagePeopleYMCAOFFICIALMusicVideo1978/Village%20People%20-%20YMCA%20OFFICIAL%20Music%20Video%201978.mp4",
|
||||
})
|
||||
time.Sleep(time.Second * 15)
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/adapter"
|
||||
"AynaLivePlayer/core/model"
|
||||
"fmt"
|
||||
"github.com/tidwall/gjson"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type Bilibili struct {
|
||||
InfoApi string
|
||||
FileApi string
|
||||
SearchApi string
|
||||
IdRegex0 *regexp.Regexp
|
||||
IdRegex1 *regexp.Regexp
|
||||
}
|
||||
|
||||
func NewBilibili(config adapter.MediaProviderConfig) adapter.MediaProvider {
|
||||
return &Bilibili{
|
||||
InfoApi: "https://www.bilibili.com/audio/music-service-c/web/song/info?sid=%s",
|
||||
FileApi: "https://api.bilibili.com/audio/music-service-c/url?device=phone&mid=8047632&mobi_app=iphone&platform=ios&privilege=2&songid=%s&quality=2",
|
||||
SearchApi: "https://api.bilibili.com/audio/music-service-c/s?search_type=music&keyword=%s&page=1&pagesize=100",
|
||||
IdRegex0: regexp.MustCompile("^[0-9]+"),
|
||||
IdRegex1: regexp.MustCompile("^au[0-9]+"),
|
||||
}
|
||||
}
|
||||
|
||||
func _newBilibili() *Bilibili {
|
||||
return &Bilibili{
|
||||
InfoApi: "https://www.bilibili.com/audio/music-service-c/web/song/info?sid=%s",
|
||||
FileApi: "https://api.bilibili.com/audio/music-service-c/url?device=phone&mid=8047632&mobi_app=iphone&platform=ios&privilege=2&songid=%s&quality=2",
|
||||
SearchApi: "https://api.bilibili.com/audio/music-service-c/s?search_type=music&keyword=%s&page=1&pagesize=100",
|
||||
IdRegex0: regexp.MustCompile("^[0-9]+"),
|
||||
IdRegex1: regexp.MustCompile("^au[0-9]+"),
|
||||
}
|
||||
}
|
||||
|
||||
var BilibiliAPI *Bilibili
|
||||
|
||||
func init() {
|
||||
BilibiliAPI = _newBilibili()
|
||||
Providers[BilibiliAPI.GetName()] = BilibiliAPI
|
||||
}
|
||||
|
||||
func (b *Bilibili) GetName() string {
|
||||
return "bilibili"
|
||||
}
|
||||
|
||||
func (b *Bilibili) MatchMedia(keyword string) *model.Media {
|
||||
if id := b.IdRegex0.FindString(keyword); id != "" {
|
||||
return &model.Media{
|
||||
Meta: model.Meta{
|
||||
Name: b.GetName(),
|
||||
Id: id,
|
||||
},
|
||||
}
|
||||
}
|
||||
if id := b.IdRegex1.FindString(keyword); id != "" {
|
||||
return &model.Media{
|
||||
Meta: model.Meta{
|
||||
Name: b.GetName(),
|
||||
Id: id[2:],
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bilibili) FormatPlaylistUrl(uri string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (b *Bilibili) GetPlaylist(playlist *model.Meta) ([]*model.Media, error) {
|
||||
return nil, ErrorExternalApi
|
||||
}
|
||||
|
||||
func (b *Bilibili) Search(keyword string) ([]*model.Media, error) {
|
||||
resp := httpGetString(fmt.Sprintf(b.SearchApi, url.QueryEscape(keyword)), map[string]string{
|
||||
"user-agent": "BiliMusic/2.233.3",
|
||||
})
|
||||
if resp == "" {
|
||||
return nil, ErrorExternalApi
|
||||
}
|
||||
result := make([]*model.Media, 0)
|
||||
gjson.Get(resp, "data.result").ForEach(func(key, value gjson.Result) bool {
|
||||
result = append(result, &model.Media{
|
||||
Title: value.Get("title").String(),
|
||||
Cover: model.Picture{Url: value.Get("cover").String()},
|
||||
Artist: value.Get("author").String(),
|
||||
Meta: model.Meta{
|
||||
Name: b.GetName(),
|
||||
Id: value.Get("id").String(),
|
||||
},
|
||||
})
|
||||
return true
|
||||
})
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (b *Bilibili) UpdateMedia(media *model.Media) error {
|
||||
resp := httpGetString(fmt.Sprintf(b.InfoApi, media.Meta.(model.Meta).Id), map[string]string{
|
||||
"user-agent": "BiliMusic/2.233.3",
|
||||
})
|
||||
if resp == "" {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
if gjson.Get(resp, "data.title").String() == "" {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
media.Title = gjson.Get(resp, "data.title").String()
|
||||
media.Cover.Url = gjson.Get(resp, "data.cover").String()
|
||||
media.Artist = gjson.Get(resp, "data.author").String()
|
||||
media.Album = media.Title
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bilibili) UpdateMediaUrl(media *model.Media) error {
|
||||
resp := httpGetString(fmt.Sprintf(b.FileApi, media.Meta.(model.Meta).Id), map[string]string{
|
||||
"user-agent": "BiliMusic/2.233.3",
|
||||
})
|
||||
|
||||
if resp == "" {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
media.Header = map[string]string{
|
||||
"user-agent": "BiliMusic/2.233.3",
|
||||
}
|
||||
uri := gjson.Get(resp, "data.cdns.0").String()
|
||||
if uri == "" {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
media.Url = uri
|
||||
return nil
|
||||
}
|
||||
func (k *Bilibili) UpdateMediaLyric(media *model.Media) error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/adapter"
|
||||
"AynaLivePlayer/core/model"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBilibili_Search(t *testing.T) {
|
||||
var api adapter.MediaProvider = BilibiliAPI
|
||||
result, err := api.Search("染 reol")
|
||||
if err != nil {
|
||||
fmt.Println(1, err)
|
||||
return
|
||||
}
|
||||
fmt.Println(result)
|
||||
media := result[0]
|
||||
fmt.Println(*media)
|
||||
err = api.UpdateMediaUrl(media)
|
||||
fmt.Println(err)
|
||||
fmt.Println(media.Url)
|
||||
}
|
||||
|
||||
func TestBilibili_GetMusicMeta(t *testing.T) {
|
||||
var api adapter.MediaProvider = BilibiliAPI
|
||||
|
||||
media := model.Media{
|
||||
Meta: model.Meta{
|
||||
Name: api.GetName(),
|
||||
Id: "1560601",
|
||||
},
|
||||
}
|
||||
err := api.UpdateMedia(&media)
|
||||
fmt.Println(err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(media)
|
||||
}
|
||||
|
||||
func TestBilibili_GetMusic(t *testing.T) {
|
||||
var api adapter.MediaProvider = BilibiliAPI
|
||||
media := model.Media{
|
||||
Meta: model.Meta{
|
||||
Name: api.GetName(),
|
||||
Id: "1560601",
|
||||
},
|
||||
}
|
||||
err := api.UpdateMedia(&media)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = api.UpdateMediaUrl(&media)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(media)
|
||||
fmt.Println(media.Url)
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/util"
|
||||
"AynaLivePlayer/core/adapter"
|
||||
"AynaLivePlayer/core/model"
|
||||
"fmt"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/tidwall/gjson"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type BilibiliVideo struct {
|
||||
InfoApi string
|
||||
FileApi string
|
||||
SearchApi string
|
||||
BVRegex *regexp.Regexp
|
||||
IdRegex *regexp.Regexp
|
||||
PageRegex *regexp.Regexp
|
||||
header map[string]string
|
||||
}
|
||||
|
||||
func NewBilibiliVideo(config adapter.MediaProviderConfig) adapter.MediaProvider {
|
||||
return &BilibiliVideo{
|
||||
InfoApi: "https://api.bilibili.com/x/web-interface/view/detail?bvid=%s&aid=&jsonp=jsonp",
|
||||
FileApi: "https://api.bilibili.com/x/player/playurl?type=&otype=json&fourk=1&qn=32&avid=&bvid=%s&cid=%s",
|
||||
SearchApi: "https://api.bilibili.com/x/web-interface/search/type?search_type=video&page=1&keyword=%s",
|
||||
BVRegex: regexp.MustCompile("^BV[0-9A-Za-z]+"),
|
||||
IdRegex: regexp.MustCompile("^BV[0-9A-Za-z]+(\\?p=[0-9]+)?"),
|
||||
PageRegex: regexp.MustCompile("p=[0-9]+"),
|
||||
header: map[string]string{
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
|
||||
"Referer": "https://www.bilibili.com/",
|
||||
"Origin": "https://www.bilibili.com",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func _newBilibiliVideo() *BilibiliVideo {
|
||||
return &BilibiliVideo{
|
||||
InfoApi: "https://api.bilibili.com/x/web-interface/view/detail?bvid=%s&aid=&jsonp=jsonp",
|
||||
FileApi: "https://api.bilibili.com/x/player/playurl?type=&otype=json&fourk=1&qn=32&avid=&bvid=%s&cid=%s",
|
||||
SearchApi: "https://api.bilibili.com/x/web-interface/search/type?search_type=video&page=1&keyword=%s",
|
||||
BVRegex: regexp.MustCompile("^BV[0-9A-Za-z]+"),
|
||||
IdRegex: regexp.MustCompile("^BV[0-9A-Za-z]+(\\?p=[0-9]+)?"),
|
||||
PageRegex: regexp.MustCompile("p=[0-9]+"),
|
||||
header: map[string]string{
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0",
|
||||
"Referer": "https://www.bilibili.com/",
|
||||
"Origin": "https://www.bilibili.com",
|
||||
"Cookie": "buvid3=9A8B3564-BDA9-407F-B45F-D5C40786CA49167618infoc;",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var BilibiliVideoAPI *BilibiliVideo
|
||||
|
||||
func init() {
|
||||
BilibiliVideoAPI = _newBilibiliVideo()
|
||||
Providers[BilibiliVideoAPI.GetName()] = BilibiliVideoAPI
|
||||
}
|
||||
|
||||
func (b *BilibiliVideo) getPage(bv string) int {
|
||||
if page := b.PageRegex.FindString(bv); page != "" {
|
||||
return util.Atoi(page[2:])
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (b *BilibiliVideo) getBv(bv string) string {
|
||||
return b.BVRegex.FindString(bv)
|
||||
}
|
||||
|
||||
func (b *BilibiliVideo) GetName() string {
|
||||
return "bilibili-video"
|
||||
}
|
||||
|
||||
func (b *BilibiliVideo) MatchMedia(keyword string) *model.Media {
|
||||
if id := b.IdRegex.FindString(keyword); id != "" {
|
||||
return &model.Media{
|
||||
Meta: model.Meta{
|
||||
Name: b.GetName(),
|
||||
Id: id,
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BilibiliVideo) GetPlaylist(playlist *model.Meta) ([]*model.Media, error) {
|
||||
return nil, ErrorExternalApi
|
||||
}
|
||||
|
||||
func (b *BilibiliVideo) FormatPlaylistUrl(uri string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (b *BilibiliVideo) Search(keyword string) ([]*model.Media, error) {
|
||||
resp := httpGetString(fmt.Sprintf(b.SearchApi, url.QueryEscape(keyword)), b.header)
|
||||
if resp == "" {
|
||||
return nil, ErrorExternalApi
|
||||
}
|
||||
jresp := gjson.Parse(resp)
|
||||
if jresp.Get("code").String() != "0" {
|
||||
return nil, ErrorExternalApi
|
||||
}
|
||||
result := make([]*model.Media, 0)
|
||||
r := regexp.MustCompile("</?em[^>]*>")
|
||||
jresp.Get("data.result").ForEach(func(key, value gjson.Result) bool {
|
||||
result = append(result, &model.Media{
|
||||
Title: r.ReplaceAllString(value.Get("title").String(), ""),
|
||||
Cover: model.Picture{Url: "https:" + value.Get("pic").String()},
|
||||
Artist: value.Get("author").String(),
|
||||
Meta: model.Meta{
|
||||
Name: b.GetName(),
|
||||
Id: value.Get("bvid").String(),
|
||||
},
|
||||
})
|
||||
return true
|
||||
})
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (b *BilibiliVideo) UpdateMedia(media *model.Media) error {
|
||||
resp := httpGetString(fmt.Sprintf(b.InfoApi, b.getBv(media.Meta.(model.Meta).Id)), nil)
|
||||
if resp == "" {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
jresp := gjson.Parse(resp)
|
||||
if jresp.Get("data.View.title").String() == "" {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
media.Title = jresp.Get("data.View.title").String()
|
||||
media.Artist = jresp.Get("data.View.owner.name").String()
|
||||
media.Cover.Url = jresp.Get("data.View.pic").String()
|
||||
media.Album = media.Title
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BilibiliVideo) UpdateMediaUrl(media *model.Media) error {
|
||||
resp := httpGetString(fmt.Sprintf(b.InfoApi, b.getBv(media.Meta.(model.Meta).Id)), nil)
|
||||
if resp == "" {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
jresp := gjson.Parse(resp)
|
||||
page := b.getPage(media.Meta.(model.Meta).Id) - 1
|
||||
cid := jresp.Get(fmt.Sprintf("data.View.pages.%d.cid", page)).String()
|
||||
if cid == "" {
|
||||
cid = jresp.Get("data.View.cid").String()
|
||||
}
|
||||
if cid == "" {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
resp = httpGetString(fmt.Sprintf(b.FileApi, b.getBv(media.Meta.(model.Meta).Id), cid), b.header)
|
||||
if resp == "" {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
jresp = gjson.Parse(resp)
|
||||
uri := jresp.Get("data.durl.0.url").String()
|
||||
if uri == "" {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
media.Url = uri
|
||||
header := make(map[string]string)
|
||||
_ = copier.Copy(&header, &b.header)
|
||||
header["Referer"] = fmt.Sprintf("https://www.bilibili.com/video/%s", b.getBv(media.Meta.(model.Meta).Id))
|
||||
media.Header = b.header
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BilibiliVideo) UpdateMediaLyric(media *model.Media) error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/adapter"
|
||||
"AynaLivePlayer/core/model"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBV_GetMusicMeta(t *testing.T) {
|
||||
var api adapter.MediaProvider = BilibiliVideoAPI
|
||||
|
||||
media := model.Media{
|
||||
Meta: model.Meta{
|
||||
Name: api.GetName(),
|
||||
Id: "BV1434y1q71P",
|
||||
},
|
||||
}
|
||||
err := api.UpdateMedia(&media)
|
||||
fmt.Println(err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(media)
|
||||
}
|
||||
|
||||
func TestBV_GetMusic(t *testing.T) {
|
||||
var api adapter.MediaProvider = BilibiliVideoAPI
|
||||
media := model.Media{
|
||||
Meta: model.Meta{
|
||||
Name: api.GetName(),
|
||||
Id: "BV1434y1q71P",
|
||||
},
|
||||
}
|
||||
err := api.UpdateMedia(&media)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = api.UpdateMediaUrl(&media)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
//fmt.Println(media)
|
||||
fmt.Println(media.Url)
|
||||
}
|
||||
|
||||
func TestBV_Regex(t *testing.T) {
|
||||
fmt.Println(regexp.MustCompile("^BV[0-9A-Za-z]+(\\?p=[0-9]+)?").FindString("BV1gA411P7ir?p=3"))
|
||||
}
|
||||
|
||||
func TestBV_GetMusicMeta2(t *testing.T) {
|
||||
var api adapter.MediaProvider = BilibiliVideoAPI
|
||||
|
||||
media := model.Media{
|
||||
Meta: model.Meta{
|
||||
Name: api.GetName(),
|
||||
Id: "BV1gA411P7ir?p=3",
|
||||
},
|
||||
}
|
||||
err := api.UpdateMedia(&media)
|
||||
fmt.Println(err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(media)
|
||||
}
|
||||
|
||||
func TestBV_GetMusic2(t *testing.T) {
|
||||
var api adapter.MediaProvider = BilibiliVideoAPI
|
||||
media := model.Media{
|
||||
Meta: model.Meta{
|
||||
Name: api.GetName(),
|
||||
Id: "BV1gA411P7ir?p=3",
|
||||
},
|
||||
}
|
||||
err := api.UpdateMedia(&media)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = api.UpdateMediaUrl(&media)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
//fmt.Println(media)
|
||||
fmt.Println(media.Url)
|
||||
}
|
||||
|
||||
func TestBV_Search(t *testing.T) {
|
||||
var api adapter.MediaProvider = BilibiliVideoAPI
|
||||
result, err := api.Search("家有女友")
|
||||
if err != nil {
|
||||
fmt.Println(1, err)
|
||||
return
|
||||
}
|
||||
fmt.Println(len(result))
|
||||
for _, r := range result {
|
||||
fmt.Println(r.Artist)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package provider
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrorExternalApi = errors.New("external api error")
|
||||
ErrorNoSuchProvider = errors.New("not such provider")
|
||||
)
|
||||
@@ -1,29 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/go-resty/resty/v2"
|
||||
"time"
|
||||
)
|
||||
|
||||
func httpGet(url string, header map[string]string) (*resty.Response, error) {
|
||||
resp, err := resty.New().
|
||||
SetTimeout(time.Second * 3).R().
|
||||
SetHeaders(header).
|
||||
Get(url)
|
||||
return resp, err
|
||||
}
|
||||
func httpGetString(url string, header map[string]string) string {
|
||||
resp, err := httpGet(url, header)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return resp.String()
|
||||
}
|
||||
|
||||
func httpHead(url string, header map[string]string) (*resty.Response, error) {
|
||||
resp, err := resty.New().
|
||||
SetTimeout(time.Second * 3).R().
|
||||
SetHeaders(header).
|
||||
Head(url)
|
||||
return resp, err
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/adapter"
|
||||
"AynaLivePlayer/core/model"
|
||||
"fmt"
|
||||
"github.com/tidwall/gjson"
|
||||
"html"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Kuwo struct {
|
||||
InfoApi string
|
||||
FileApi string
|
||||
SearchCookie string
|
||||
SearchApi string
|
||||
LyricApi string
|
||||
PlaylistApi string
|
||||
PlaylistRegex0 *regexp.Regexp
|
||||
PlaylistRegex1 *regexp.Regexp
|
||||
IdRegex0 *regexp.Regexp
|
||||
IdRegex1 *regexp.Regexp
|
||||
}
|
||||
|
||||
func NewKuwo(config adapter.MediaProviderConfig) adapter.MediaProvider {
|
||||
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",
|
||||
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]+"),
|
||||
IdRegex0: regexp.MustCompile("^[0-9]+"),
|
||||
IdRegex1: regexp.MustCompile("^kw[0-9]+"),
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
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]+"),
|
||||
IdRegex0: regexp.MustCompile("^[0-9]+"),
|
||||
IdRegex1: regexp.MustCompile("^kw[0-9]+"),
|
||||
}
|
||||
}
|
||||
|
||||
var KuwoAPI *Kuwo
|
||||
|
||||
func init() {
|
||||
KuwoAPI = _newKuwo()
|
||||
Providers[KuwoAPI.GetName()] = KuwoAPI
|
||||
}
|
||||
|
||||
func (k *Kuwo) GetName() string {
|
||||
return "kuwo"
|
||||
}
|
||||
|
||||
func (k *Kuwo) MatchMedia(keyword string) *model.Media {
|
||||
if id := k.IdRegex0.FindString(keyword); id != "" {
|
||||
return &model.Media{
|
||||
Meta: model.Meta{
|
||||
Name: k.GetName(),
|
||||
Id: id,
|
||||
},
|
||||
}
|
||||
}
|
||||
if id := k.IdRegex1.FindString(keyword); id != "" {
|
||||
return &model.Media{
|
||||
Meta: model.Meta{
|
||||
Name: k.GetName(),
|
||||
Id: id[2:],
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
return httpGetString(url, map[string]string{
|
||||
"cookie": "kw_token=" + "95MWTYC4FP",
|
||||
"csrf": "95MWTYC4FP",
|
||||
"referer": "http://www.kuwo.cn/",
|
||||
})
|
||||
}
|
||||
|
||||
func (k *Kuwo) Search(keyword string) ([]*model.Media, error) {
|
||||
resp := k._kuwoGet(fmt.Sprintf(k.SearchApi, url.QueryEscape(keyword), 1, 64))
|
||||
if resp == "" {
|
||||
return nil, ErrorExternalApi
|
||||
}
|
||||
result := make([]*model.Media, 0)
|
||||
gjson.Parse(resp).Get("data.list").ForEach(func(key, value gjson.Result) bool {
|
||||
result = append(result, &model.Media{
|
||||
Title: html.UnescapeString(value.Get("name").String()),
|
||||
Cover: model.Picture{Url: value.Get("pic").String()},
|
||||
Artist: value.Get("artist").String(),
|
||||
Album: value.Get("album").String(),
|
||||
Meta: model.Meta{
|
||||
Name: k.GetName(),
|
||||
Id: value.Get("rid").String(),
|
||||
},
|
||||
})
|
||||
return true
|
||||
})
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (k *Kuwo) UpdateMedia(media *model.Media) error {
|
||||
resp := k._kuwoGet(fmt.Sprintf(k.InfoApi, media.Meta.(model.Meta).Id))
|
||||
if resp == "" {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
jresp := gjson.Parse(resp)
|
||||
if jresp.Get("data.musicrid").String() == "" {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
media.Title = html.UnescapeString(jresp.Get("data.name").String())
|
||||
media.Cover.Url = jresp.Get("data.pic").String()
|
||||
media.Artist = jresp.Get("data.artist").String()
|
||||
media.Album = jresp.Get("data.album").String()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Kuwo) UpdateMediaUrl(media *model.Media) error {
|
||||
result := httpGetString(fmt.Sprintf(k.FileApi, media.Meta.(model.Meta).Id), nil)
|
||||
if result == "" {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
media.Url = result
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Kuwo) UpdateMediaLyric(media *model.Media) error {
|
||||
result := httpGetString(fmt.Sprintf(k.LyricApi, media.Meta.(model.Meta).Id), nil)
|
||||
if result == "" {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
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(playlist *model.Meta) ([]*model.Media, error) {
|
||||
medias := make([]*model.Media, 0)
|
||||
var resp string
|
||||
var jresp gjson.Result
|
||||
for i := 1; i <= 20; i++ {
|
||||
resp = k._kuwoGet(fmt.Sprintf(k.PlaylistApi, playlist.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,
|
||||
&model.Media{
|
||||
Title: html.UnescapeString(value.Get("name").String()),
|
||||
Artist: value.Get("artist").String(),
|
||||
Cover: model.Picture{Url: value.Get("pic").String()},
|
||||
Album: value.Get("album").String(),
|
||||
Meta: model.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
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/adapter"
|
||||
"AynaLivePlayer/core/model"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestKuwo_Search(t *testing.T) {
|
||||
var api adapter.MediaProvider = KuwoAPI
|
||||
result, err := api.Search("染 reol")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(result)
|
||||
media := result[0]
|
||||
err = api.UpdateMediaUrl(media)
|
||||
fmt.Println(err)
|
||||
fmt.Println(media.Url)
|
||||
}
|
||||
|
||||
func TestKuwo_GetMusicMeta(t *testing.T) {
|
||||
var api adapter.MediaProvider = KuwoAPI
|
||||
|
||||
media := model.Media{
|
||||
Meta: model.Meta{
|
||||
Name: api.GetName(),
|
||||
Id: "22804772",
|
||||
},
|
||||
}
|
||||
err := api.UpdateMedia(&media)
|
||||
fmt.Println(err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(media)
|
||||
}
|
||||
|
||||
func TestKuwo_GetMusic(t *testing.T) {
|
||||
var api adapter.MediaProvider = KuwoAPI
|
||||
media := model.Media{
|
||||
Meta: model.Meta{
|
||||
Name: api.GetName(),
|
||||
Id: "22804772",
|
||||
},
|
||||
}
|
||||
err := api.UpdateMedia(&media)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = api.UpdateMediaUrl(&media)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(media)
|
||||
fmt.Println(media.Url)
|
||||
}
|
||||
|
||||
func TestKuwo_UpdateMediaLyric(t *testing.T) {
|
||||
var api adapter.MediaProvider = KuwoAPI
|
||||
media := model.Media{
|
||||
Meta: model.Meta{
|
||||
Name: api.GetName(),
|
||||
Id: "22804772",
|
||||
},
|
||||
}
|
||||
err := api.UpdateMediaLyric(&media)
|
||||
fmt.Println(err)
|
||||
fmt.Println(media.Lyric)
|
||||
}
|
||||
|
||||
func TestKuwo_GetPlaylist(t *testing.T) {
|
||||
var api adapter.MediaProvider = KuwoAPI
|
||||
playlist, err := api.GetPlaylist(&model.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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/util"
|
||||
"AynaLivePlayer/core/adapter"
|
||||
"AynaLivePlayer/core/model"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type _LocalPlaylist struct {
|
||||
Name string
|
||||
Medias []*model.Media
|
||||
}
|
||||
|
||||
type Local struct {
|
||||
localDir string
|
||||
Playlists []*_LocalPlaylist
|
||||
}
|
||||
|
||||
func NewLocalCtor(config adapter.MediaProviderConfig) adapter.MediaProvider {
|
||||
localDir, ok := config["local_dir"]
|
||||
if !ok {
|
||||
localDir = "./local"
|
||||
}
|
||||
l := &Local{Playlists: make([]*_LocalPlaylist, 0), localDir: localDir}
|
||||
if err := os.MkdirAll(localDir, 0755); err != nil {
|
||||
return l
|
||||
}
|
||||
for _, n := range getPlaylistNames(localDir) {
|
||||
l.Playlists = append(l.Playlists, &_LocalPlaylist{Name: n})
|
||||
}
|
||||
for i, _ := range l.Playlists {
|
||||
_ = readLocalPlaylist(localDir, l.Playlists[i])
|
||||
}
|
||||
LocalAPI = l
|
||||
Providers[LocalAPI.GetName()] = LocalAPI
|
||||
return l
|
||||
}
|
||||
|
||||
var LocalAPI *Local
|
||||
|
||||
func NewLocal(localdir string) *Local {
|
||||
l := &Local{Playlists: make([]*_LocalPlaylist, 0), localDir: localdir}
|
||||
if err := os.MkdirAll(localdir, 0755); err != nil {
|
||||
return l
|
||||
}
|
||||
for _, n := range getPlaylistNames(localdir) {
|
||||
l.Playlists = append(l.Playlists, &_LocalPlaylist{Name: n})
|
||||
}
|
||||
for i, _ := range l.Playlists {
|
||||
_ = readLocalPlaylist(localdir, l.Playlists[i])
|
||||
}
|
||||
LocalAPI = l
|
||||
Providers[LocalAPI.GetName()] = LocalAPI
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Local) GetName() string {
|
||||
return "local"
|
||||
}
|
||||
|
||||
func (l *Local) MatchMedia(keyword string) *model.Media {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Local) UpdateMediaLyric(media *model.Media) error {
|
||||
// already update in UpdateMedia, do nothing
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Local) FormatPlaylistUrl(uri string) string {
|
||||
return uri
|
||||
}
|
||||
|
||||
func (l *Local) GetPlaylist(playlist *model.Meta) ([]*model.Media, error) {
|
||||
var pl *_LocalPlaylist = nil
|
||||
for _, p := range l.Playlists {
|
||||
if p.Name == playlist.Id {
|
||||
pl = p
|
||||
}
|
||||
}
|
||||
if pl == nil {
|
||||
l.Playlists = append(l.Playlists, &_LocalPlaylist{Name: playlist.Id})
|
||||
pl = l.Playlists[len(l.Playlists)-1]
|
||||
}
|
||||
if readLocalPlaylist(l.localDir, pl) != nil {
|
||||
return nil, ErrorExternalApi
|
||||
}
|
||||
return pl.Medias, nil
|
||||
}
|
||||
|
||||
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
|
||||
}, 0)
|
||||
keywords := strings.Split(keyword, " ")
|
||||
for _, p := range l.Playlists {
|
||||
for _, m := range p.Medias {
|
||||
title := strings.ToLower(m.Title)
|
||||
artist := strings.ToLower(m.Artist)
|
||||
n := 0
|
||||
for _, k := range keywords {
|
||||
kw := strings.ToLower(k)
|
||||
if strings.Contains(title, kw) || strings.Contains(artist, kw) {
|
||||
n++
|
||||
}
|
||||
if kw == title {
|
||||
n += 3
|
||||
}
|
||||
}
|
||||
if n > 0 {
|
||||
result = append(result, struct {
|
||||
M *model.Media
|
||||
N int
|
||||
}{M: m, N: n})
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i].N > result[j].N
|
||||
})
|
||||
medias := make([]*model.Media, len(result))
|
||||
for i, r := range result {
|
||||
medias[i] = r.M.Copy()
|
||||
}
|
||||
return medias, nil
|
||||
}
|
||||
|
||||
func (l *Local) UpdateMedia(media *model.Media) error {
|
||||
mediaPath := media.Meta.(model.Meta).Id
|
||||
_, err := os.Stat(mediaPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return readMediaFile(media)
|
||||
}
|
||||
|
||||
func (l *Local) UpdateMediaUrl(media *model.Media) error {
|
||||
mediaPath := media.Meta.(model.Meta).Id
|
||||
_, err := os.Stat(mediaPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
media.Url = mediaPath
|
||||
return nil
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/util"
|
||||
"AynaLivePlayer/core/model"
|
||||
"github.com/dhowden/tag"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func getPlaylistNames(localdir string) []string {
|
||||
names := make([]string, 0)
|
||||
items, _ := ioutil.ReadDir(localdir)
|
||||
for _, item := range items {
|
||||
if item.IsDir() {
|
||||
names = append(names, item.Name())
|
||||
}
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// readLocalPlaylist read files under a directory
|
||||
// and return a _LocalPlaylist object.
|
||||
// This function assume this directory exists
|
||||
func readLocalPlaylist(localdir string, playlist *_LocalPlaylist) error {
|
||||
p1th := playlist.Name
|
||||
playlist.Medias = make([]*model.Media, 0)
|
||||
fullPath := filepath.Join(localdir, p1th)
|
||||
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
items, _ := ioutil.ReadDir(fullPath)
|
||||
for _, item := range items {
|
||||
// if item is a file, read file
|
||||
if !item.IsDir() {
|
||||
fn := item.Name()
|
||||
media := model.Media{
|
||||
Meta: model.Meta{
|
||||
Name: LocalAPI.GetName(),
|
||||
Id: filepath.Join(fullPath, fn),
|
||||
},
|
||||
}
|
||||
if readMediaFile(&media) != nil {
|
||||
continue
|
||||
}
|
||||
playlist.Medias = append(playlist.Medias, &media)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readMediaFile(media *model.Media) error {
|
||||
p := media.Meta.(model.Meta).Id
|
||||
f, err := os.Open(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
meta, err := tag.ReadFrom(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
media.Title = util.GetOrDefault(meta.Title(), filepath.Base(p))
|
||||
media.Artist = util.GetOrDefault(meta.Artist(), "Unknown")
|
||||
media.Album = util.GetOrDefault(meta.Album(), "Unknown")
|
||||
media.Lyric = meta.Lyrics()
|
||||
if meta.Picture() != nil {
|
||||
media.Cover.Data = meta.Picture().Data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLocal_Read(t *testing.T) {
|
||||
items, _ := ioutil.ReadDir(".")
|
||||
for _, item := range items {
|
||||
if item.IsDir() {
|
||||
subitems, _ := ioutil.ReadDir(item.Name())
|
||||
for _, subitem := range subitems {
|
||||
if !subitem.IsDir() {
|
||||
// handle file there
|
||||
fmt.Println(item.Name() + "/" + subitem.Name())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// handle file there
|
||||
fmt.Println(item.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/util"
|
||||
"AynaLivePlayer/core/adapter"
|
||||
"AynaLivePlayer/core/model"
|
||||
neteaseApi "github.com/XiaoMengXinX/Music163Api-Go/api"
|
||||
neteaseTypes "github.com/XiaoMengXinX/Music163Api-Go/types"
|
||||
neteaseUtil "github.com/XiaoMengXinX/Music163Api-Go/utils"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Netease struct {
|
||||
PlaylistRegex0 *regexp.Regexp
|
||||
PlaylistRegex1 *regexp.Regexp
|
||||
ReqData neteaseUtil.RequestData
|
||||
IdRegex0 *regexp.Regexp
|
||||
IdRegex1 *regexp.Regexp
|
||||
loginStatus neteaseTypes.LoginStatusData
|
||||
}
|
||||
|
||||
func NewNetease(config adapter.MediaProviderConfig) adapter.MediaProvider {
|
||||
return &Netease{
|
||||
PlaylistRegex0: regexp.MustCompile("^[0-9]+$"),
|
||||
// https://music.163.com/playlist?id=2382819181&userid=95906480
|
||||
PlaylistRegex1: regexp.MustCompile("playlist\\?id=[0-9]+"),
|
||||
ReqData: neteaseUtil.RequestData{
|
||||
Headers: neteaseUtil.Headers{
|
||||
{
|
||||
"X-Real-IP",
|
||||
"118.88.88.88",
|
||||
},
|
||||
},
|
||||
},
|
||||
IdRegex0: regexp.MustCompile("^[0-9]+"),
|
||||
IdRegex1: regexp.MustCompile("^wy[0-9]+"),
|
||||
}
|
||||
}
|
||||
|
||||
func _newNetease() *Netease {
|
||||
return &Netease{
|
||||
PlaylistRegex0: regexp.MustCompile("^[0-9]+$"),
|
||||
// https://music.163.com/playlist?id=2382819181&userid=95906480
|
||||
PlaylistRegex1: regexp.MustCompile("playlist\\?id=[0-9]+"),
|
||||
ReqData: neteaseUtil.RequestData{
|
||||
Headers: neteaseUtil.Headers{
|
||||
{
|
||||
"X-Real-IP",
|
||||
"118.88.88.88",
|
||||
},
|
||||
},
|
||||
},
|
||||
IdRegex0: regexp.MustCompile("^[0-9]+"),
|
||||
IdRegex1: regexp.MustCompile("^wy[0-9]+"),
|
||||
}
|
||||
}
|
||||
|
||||
var NeteaseAPI *Netease
|
||||
|
||||
func init() {
|
||||
NeteaseAPI = _newNetease()
|
||||
Providers[NeteaseAPI.GetName()] = NeteaseAPI
|
||||
}
|
||||
|
||||
// Netease private helper method
|
||||
|
||||
func _neteaseGetArtistNames(data neteaseTypes.SongDetailData) string {
|
||||
artists := make([]string, 0)
|
||||
for _, a := range data.Ar {
|
||||
artists = append(artists, a.Name)
|
||||
}
|
||||
return strings.Join(artists, ",")
|
||||
}
|
||||
|
||||
// MediaProvider implementation
|
||||
|
||||
func (n *Netease) GetName() string {
|
||||
return "netease"
|
||||
}
|
||||
|
||||
func (n *Netease) MatchMedia(keyword string) *model.Media {
|
||||
if id := n.IdRegex0.FindString(keyword); id != "" {
|
||||
return &model.Media{
|
||||
Meta: model.Meta{
|
||||
Name: n.GetName(),
|
||||
Id: id,
|
||||
},
|
||||
}
|
||||
}
|
||||
if id := n.IdRegex1.FindString(keyword); id != "" {
|
||||
return &model.Media{
|
||||
Meta: model.Meta{
|
||||
Name: n.GetName(),
|
||||
Id: id[2:],
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Netease) FormatPlaylistUrl(uri string) string {
|
||||
var id string
|
||||
id = n.PlaylistRegex0.FindString(uri)
|
||||
if id != "" {
|
||||
return id
|
||||
}
|
||||
id = n.PlaylistRegex1.FindString(uri)
|
||||
if id != "" {
|
||||
return id[12:]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (n *Netease) GetPlaylist(playlist *model.Meta) ([]*model.Media, error) {
|
||||
result, err := neteaseApi.GetPlaylistDetail(
|
||||
n.ReqData, util.Atoi(playlist.Id))
|
||||
if err != nil || result.Code != 200 {
|
||||
return nil, ErrorExternalApi
|
||||
}
|
||||
cnt := len(result.Playlist.TrackIds)
|
||||
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
|
||||
}
|
||||
medias := make([]*model.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, &model.Media{
|
||||
Title: result2.Songs[i].Name,
|
||||
Artist: _neteaseGetArtistNames(result2.Songs[i]),
|
||||
Cover: model.Picture{Url: result2.Songs[i].Al.PicUrl},
|
||||
Album: result2.Songs[i].Al.Name,
|
||||
Url: "",
|
||||
Header: nil,
|
||||
User: nil,
|
||||
Meta: model.Meta{
|
||||
Name: n.GetName(),
|
||||
Id: strconv.Itoa(result2.Songs[i].Id),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(medias) == 0 {
|
||||
return nil, ErrorExternalApi
|
||||
}
|
||||
return medias, nil
|
||||
}
|
||||
|
||||
func (n *Netease) Search(keyword string) ([]*model.Media, error) {
|
||||
rawResult, err := neteaseApi.SearchSong(
|
||||
n.ReqData,
|
||||
neteaseApi.SearchSongConfig{
|
||||
Keyword: keyword,
|
||||
Limit: 30,
|
||||
Offset: 0,
|
||||
})
|
||||
if err != nil || rawResult.Code != 200 {
|
||||
return nil, ErrorExternalApi
|
||||
}
|
||||
medias := make([]*model.Media, 0)
|
||||
for _, song := range rawResult.Result.Songs {
|
||||
artists := make([]string, 0)
|
||||
for _, a := range song.Artists {
|
||||
artists = append(artists, a.Name)
|
||||
}
|
||||
medias = append(medias, &model.Media{
|
||||
Title: song.Name,
|
||||
Artist: strings.Join(artists, ","),
|
||||
Cover: model.Picture{},
|
||||
Album: song.Album.Name,
|
||||
Url: "",
|
||||
Header: nil,
|
||||
Meta: model.Meta{
|
||||
Name: n.GetName(),
|
||||
Id: strconv.Itoa(song.Id),
|
||||
},
|
||||
})
|
||||
}
|
||||
return medias, nil
|
||||
}
|
||||
|
||||
func (n *Netease) UpdateMedia(media *model.Media) error {
|
||||
result, err := neteaseApi.GetSongDetail(
|
||||
n.ReqData,
|
||||
[]int{util.Atoi(media.Meta.(model.Meta).Id)})
|
||||
if err != nil || result.Code != 200 {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
if len(result.Songs) == 0 {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
media.Title = result.Songs[0].Name
|
||||
media.Cover.Url = result.Songs[0].Al.PicUrl
|
||||
media.Album = result.Songs[0].Al.Name
|
||||
media.Artist = _neteaseGetArtistNames(result.Songs[0])
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Netease) UpdateMediaUrl(media *model.Media) error {
|
||||
result, err := neteaseApi.GetSongURL(
|
||||
n.ReqData,
|
||||
neteaseApi.SongURLConfig{Ids: []int{util.Atoi(media.Meta.(model.Meta).Id)}})
|
||||
if err != nil || result.Code != 200 {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
if len(result.Data) == 0 {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
if result.Data[0].Code != 200 {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
media.Url = result.Data[0].Url
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Netease) UpdateMediaLyric(media *model.Media) error {
|
||||
result, err := neteaseApi.GetSongLyric(n.ReqData, util.Atoi(media.Meta.(model.Meta).Id))
|
||||
if err != nil || result.Code != 200 {
|
||||
return ErrorExternalApi
|
||||
}
|
||||
media.Lyric = result.Lrc.Lyric
|
||||
return nil
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
neteaseApi "github.com/XiaoMengXinX/Music163Api-Go/api"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Netease other method
|
||||
|
||||
func (n *Netease) UpdateStatus() {
|
||||
status, _ := neteaseApi.GetLoginStatus(n.ReqData)
|
||||
n.loginStatus = status
|
||||
}
|
||||
|
||||
// IsLogin check if current cookie is a login user
|
||||
func (n *Netease) IsLogin() bool {
|
||||
return n.loginStatus.Profile.UserId != 0
|
||||
}
|
||||
|
||||
func (n *Netease) Nickname() string {
|
||||
return n.loginStatus.Profile.Nickname
|
||||
}
|
||||
|
||||
func (n *Netease) GetQrLoginKey() string {
|
||||
unikey, err := neteaseApi.GetQrUnikey(n.ReqData)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return unikey.Unikey
|
||||
}
|
||||
|
||||
func (n *Netease) GetQrLoginUrl(key string) string {
|
||||
return fmt.Sprintf("https://music.163.com/login?codekey=%s", key)
|
||||
}
|
||||
|
||||
func (n *Netease) CheckQrLogin(key string) (bool, string) {
|
||||
login, h, err := neteaseApi.CheckQrLogin(n.ReqData, key)
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
// if login.Code == 800 || login.Code == 803. login success
|
||||
if login.Code != 800 && login.Code != 803 {
|
||||
return false, login.Message
|
||||
}
|
||||
cookies := make([]*http.Cookie, 0)
|
||||
for _, c := range (&http.Response{Header: h}).Cookies() {
|
||||
if c.Name == "MUSIC_U" || c.Name == "__csrf" {
|
||||
cookies = append(cookies, c)
|
||||
}
|
||||
}
|
||||
n.ReqData.Cookies = cookies
|
||||
return true, login.Message
|
||||
}
|
||||
|
||||
func (n *Netease) Logout() {
|
||||
n.ReqData.Cookies = []*http.Cookie{
|
||||
{Name: "MUSIC_U", Value: ""},
|
||||
{Name: "__csrf", Value: ""},
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/adapter"
|
||||
"AynaLivePlayer/core/model"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNetease_Search(t *testing.T) {
|
||||
var api adapter.MediaProvider = NeteaseAPI
|
||||
result, err := api.Search("染 reol")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(result)
|
||||
media := result[0]
|
||||
fmt.Println(media)
|
||||
err = api.UpdateMediaUrl(media)
|
||||
fmt.Println(err)
|
||||
fmt.Println(media.Url)
|
||||
}
|
||||
|
||||
func TestNetease_Search2(t *testing.T) {
|
||||
var api adapter.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 adapter.MediaProvider = NeteaseAPI
|
||||
|
||||
media := model.Media{
|
||||
Meta: model.Meta{
|
||||
Name: api.GetName(),
|
||||
Id: "33516503",
|
||||
},
|
||||
}
|
||||
err := api.UpdateMedia(&media)
|
||||
fmt.Println(err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(media)
|
||||
}
|
||||
|
||||
func TestNetease_GetMusic(t *testing.T) {
|
||||
var api adapter.MediaProvider = NeteaseAPI
|
||||
media := model.Media{
|
||||
Meta: model.Meta{
|
||||
Name: api.GetName(),
|
||||
Id: "33516503",
|
||||
},
|
||||
}
|
||||
err := api.UpdateMedia(&media)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = api.UpdateMediaUrl(&media)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(media)
|
||||
fmt.Println(media.Url)
|
||||
}
|
||||
|
||||
func TestNetease_GetPlaylist(t *testing.T) {
|
||||
var api adapter.MediaProvider = NeteaseAPI
|
||||
playlist, err := api.GetPlaylist(&model.Meta{
|
||||
Name: api.GetName(),
|
||||
//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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNetease_UpdateMediaLyric(t *testing.T) {
|
||||
var api adapter.MediaProvider = NeteaseAPI
|
||||
media := model.Media{
|
||||
Meta: model.Meta{
|
||||
Name: api.GetName(),
|
||||
Id: "33516503",
|
||||
},
|
||||
}
|
||||
err := api.UpdateMediaLyric(&media)
|
||||
fmt.Println(err)
|
||||
fmt.Println(media.Lyric)
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/adapter"
|
||||
model "AynaLivePlayer/core/model"
|
||||
)
|
||||
|
||||
var RegisteredProviders = map[string]adapter.MediaProviderCtor{
|
||||
"netease": NewNetease,
|
||||
"local": NewLocalCtor,
|
||||
"kuwo": NewKuwo,
|
||||
"bilibili": NewBilibili,
|
||||
"bilibili-video": NewBilibiliVideo,
|
||||
}
|
||||
|
||||
var Providers map[string]adapter.MediaProvider = make(map[string]adapter.MediaProvider)
|
||||
|
||||
func GetPlaylist(meta *model.Meta) ([]*model.Media, error) {
|
||||
if v, ok := Providers[meta.Name]; ok {
|
||||
return v.GetPlaylist(meta)
|
||||
}
|
||||
return nil, ErrorNoSuchProvider
|
||||
}
|
||||
|
||||
func FormatPlaylistUrl(pname, uri string) (string, error) {
|
||||
if v, ok := Providers[pname]; ok {
|
||||
return v.FormatPlaylistUrl(uri), nil
|
||||
}
|
||||
return "", ErrorNoSuchProvider
|
||||
}
|
||||
|
||||
func MatchMedia(provider string, keyword string) *model.Media {
|
||||
if v, ok := Providers[provider]; ok {
|
||||
return v.MatchMedia(keyword)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Search(provider string, keyword string) ([]*model.Media, error) {
|
||||
if v, ok := Providers[provider]; ok {
|
||||
return v.Search(keyword)
|
||||
}
|
||||
return nil, ErrorNoSuchProvider
|
||||
}
|
||||
|
||||
func UpdateMedia(media *model.Media) error {
|
||||
if v, ok := Providers[media.Meta.(model.Meta).Name]; ok {
|
||||
return v.UpdateMedia(media)
|
||||
}
|
||||
return ErrorNoSuchProvider
|
||||
}
|
||||
|
||||
func UpdateMediaUrl(media *model.Media) error {
|
||||
if v, ok := Providers[media.Meta.(model.Meta).Name]; ok {
|
||||
return v.UpdateMediaUrl(media)
|
||||
}
|
||||
return ErrorNoSuchProvider
|
||||
}
|
||||
|
||||
func UpdateMediaLyric(media *model.Media) error {
|
||||
if v, ok := Providers[media.Meta.(model.Meta).Name]; ok {
|
||||
return v.UpdateMediaLyric(media)
|
||||
}
|
||||
return ErrorNoSuchProvider
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/util"
|
||||
"AynaLivePlayer/core/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
|
||||
}
|
||||
112
app/main.go
Normal file
112
app/main.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui"
|
||||
"AynaLivePlayer/internal"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"AynaLivePlayer/pkg/logger"
|
||||
loggerRepo "AynaLivePlayer/pkg/logger/repository"
|
||||
"flag"
|
||||
)
|
||||
|
||||
var dev = flag.Bool("dev", false, "dev")
|
||||
|
||||
type _LogConfig struct {
|
||||
config.BaseConfig
|
||||
Path string
|
||||
Level logger.LogLevel
|
||||
RedirectStderr bool
|
||||
MaxSize int64
|
||||
}
|
||||
|
||||
func (c *_LogConfig) Name() string {
|
||||
return "Log"
|
||||
}
|
||||
|
||||
var Log = &_LogConfig{
|
||||
Path: "./log.txt",
|
||||
Level: logger.LogLevelInfo,
|
||||
RedirectStderr: false, // this should be true if it is in production mode.
|
||||
MaxSize: 5,
|
||||
}
|
||||
|
||||
//func createController(log adapter.ILogger) adapter.IControlBridge {
|
||||
// logbridge := log.WithModule("ControlBridge")
|
||||
// em := event.MainManager.NewChildManager()
|
||||
// liveroom := internal.NewLiveRoomController(
|
||||
// logbridge)
|
||||
// lyric := internal.NewLyricLoader()
|
||||
// provider := internal.NewProviderController(logbridge)
|
||||
// playlist := internal.NewPlaylistController(em, logbridge, provider)
|
||||
// plugin := internal.NewPluginController(logbridge)
|
||||
// mpvPlayer := player.NewMpvPlayer(em, logbridge)
|
||||
// playControl := internal.NewPlayerController(mpvPlayer, playlist, lyric, provider, logbridge)
|
||||
// ctr := internal.NewController(
|
||||
// liveroom, playControl, playlist, provider, plugin,
|
||||
// logbridge,
|
||||
// )
|
||||
// return ctr
|
||||
//}
|
||||
|
||||
func setupGlobal() {
|
||||
global.EventManager = event.NewManger(128, 16)
|
||||
global.Logger = loggerRepo.NewZapColoredLogger()
|
||||
global.Logger.SetLogLevel(Log.Level)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
config.LoadFromFile(config.ConfigPath)
|
||||
config.LoadConfig(Log)
|
||||
i18n.LoadLanguage(config.General.Language)
|
||||
setupGlobal()
|
||||
global.Logger.Info("================Program Start================")
|
||||
global.Logger.Infof("================Current Version: %s================", model.Version(config.Version))
|
||||
//mainController := createController(log)
|
||||
internal.Initialize()
|
||||
gui.Initialize()
|
||||
global.EventManager.Start()
|
||||
//plugins := []adapter.Plugin{diange.NewDiange(mainController), qiege.NewQiege(mainController),
|
||||
// textinfo.NewTextInfo(mainController), webinfo.NewWebInfo(mainController),
|
||||
// wylogin.NewWYLogin(mainController)}
|
||||
//mainController.LoadPlugins(plugins...)
|
||||
gui.MainWindow.ShowAndRun()
|
||||
internal.Stop()
|
||||
global.EventManager.Stop()
|
||||
if *dev {
|
||||
i18n.SaveTranslation()
|
||||
}
|
||||
_ = config.SaveToConfigFile(config.ConfigPath)
|
||||
global.Logger.Info("================Program End================")
|
||||
}
|
||||
|
||||
////go:embed all:../webgui/frontend/dist
|
||||
//var assets embed.FS
|
||||
//
|
||||
//func main() {
|
||||
// // Create an instance of the app structure
|
||||
// app := webgui.NewApp()
|
||||
//
|
||||
// // Create application with options
|
||||
// err := wails.Run(&options.App{
|
||||
// Title: "AynaLivePlayer",
|
||||
// Width: 1024,
|
||||
// Height: 768,
|
||||
// AssetServer: &assetserver.Options{
|
||||
// Assets: assets,
|
||||
// },
|
||||
// BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
|
||||
// OnStartup: app.Startup,
|
||||
// Bind: []interface{}{
|
||||
// app,
|
||||
// },
|
||||
// })
|
||||
//
|
||||
// if err != nil {
|
||||
// println("Error:", err.Error())
|
||||
// }
|
||||
//}
|
||||
68
assets/scripts/utils/ttc2ttf.py
Normal file
68
assets/scripts/utils/ttc2ttf.py
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#First released as C++ program by Hiroyuki Tsutsumi as part of the free software suite “Beer”
|
||||
#I thought porting it to Python could be both a challenge and useful
|
||||
|
||||
from sys import argv, exit, getsizeof
|
||||
from struct import pack_into, unpack_from
|
||||
|
||||
def ceil4(n):
|
||||
"""returns the next integer which is a multiple of 4"""
|
||||
return (n + 3) & ~3
|
||||
|
||||
if len(argv)!=2:
|
||||
print("Usage: %s FontCollection.ttc" % argv)
|
||||
exit(2)
|
||||
|
||||
filename = argv[1]
|
||||
in_file = open(filename, "rb")
|
||||
buf = in_file.read()
|
||||
in_file.close()
|
||||
|
||||
if filename.lower().endswith(".ttc"):
|
||||
filename = filename[:-4]
|
||||
|
||||
|
||||
if buf[:4] != b"ttcf":
|
||||
out_filename = "%s.ttf" % filename
|
||||
out_file = open(out_filename, "wb")
|
||||
out_file.write(buf)
|
||||
#end, so we don’t have to close the files or call exit() here
|
||||
else:
|
||||
ttf_count = unpack_from("!L", buf, 0x08)[0]
|
||||
print("Anzahl enthaltener TTF-Dateien: %s" % ttf_count)
|
||||
ttf_offset_array = unpack_from("!"+ttf_count*"L", buf, 0x0C)
|
||||
for i in range(ttf_count):
|
||||
print("Extrahiere TTF #%s:" % (i+1))
|
||||
table_header_offset = ttf_offset_array[i]
|
||||
print("\tHeader beginnt bei Byte %s" % table_header_offset)
|
||||
table_count = unpack_from("!H", buf, table_header_offset+0x04)[0]
|
||||
header_length = 0x0C + table_count * 0x10
|
||||
print("\tHeaderlänge: %s Byte" % header_length)
|
||||
|
||||
table_length = 0
|
||||
for j in range(table_count):
|
||||
length = unpack_from("!L", buf, table_header_offset+0x0C+0x0C+j*0x10)[0]
|
||||
table_length += ceil4(length)
|
||||
|
||||
total_length = header_length + table_length
|
||||
new_buf = bytearray(total_length)
|
||||
header = unpack_from(header_length*"c", buf, table_header_offset)
|
||||
pack_into(header_length*"c", new_buf, 0, *header)
|
||||
current_offset = header_length
|
||||
|
||||
for j in range(table_count):
|
||||
offset = unpack_from("!L", buf, table_header_offset+0x0C+0x08+j*0x10)[0]
|
||||
length = unpack_from("!L", buf, table_header_offset+0x0C+0x0C+j*0x10)[0]
|
||||
pack_into("!L", new_buf, 0x0C+0x08+j*0x10, current_offset)
|
||||
current_table = unpack_from(length*"c", buf, offset)
|
||||
pack_into(length*"c", new_buf, current_offset, *current_table)
|
||||
|
||||
#table_checksum = sum(unpack_from("!"+("L"*length), new_buf, current_offset))
|
||||
#pack_into("!L", new_buf, 0x0C+0x04+j*0x10, table_checksum)
|
||||
|
||||
current_offset += ceil4(length)
|
||||
|
||||
out_file = open("%s%d.ttf"%(filename, i), "wb")
|
||||
out_file.write(new_buf)
|
||||
@@ -4,6 +4,10 @@
|
||||
"zh-CN"
|
||||
],
|
||||
"Messages": {
|
||||
"default web protocol. enter room id to connect.": {
|
||||
"en": "default web protocol. enter room id to connect.",
|
||||
"zh-CN": "网页弹幕协议,请输入房间号"
|
||||
},
|
||||
"gui.config.basic.audio_device": {
|
||||
"en": "Audio Device",
|
||||
"zh-CN": "音频输出设备"
|
||||
@@ -72,6 +76,10 @@
|
||||
"en": "User",
|
||||
"zh-CN": "用户"
|
||||
},
|
||||
"gui.lyric.title": {
|
||||
"en": "Lyric",
|
||||
"zh-CN": "歌词"
|
||||
},
|
||||
"gui.player.button.lrc": {
|
||||
"en": "lrc",
|
||||
"zh-CN": "歌词"
|
||||
@@ -168,6 +176,10 @@
|
||||
"en": "Room ID",
|
||||
"zh-CN": "房间号"
|
||||
},
|
||||
"gui.room.add.name": {
|
||||
"en": "Display Name",
|
||||
"zh-CN": "显示名"
|
||||
},
|
||||
"gui.room.add.prompt": {
|
||||
"en": "enter room id",
|
||||
"zh-CN": "填入房间号"
|
||||
@@ -272,6 +284,10 @@
|
||||
"en": "New Version Available",
|
||||
"zh-CN": "有新版本可用"
|
||||
},
|
||||
"open bilibili live protocol. enter client key to connect.": {
|
||||
"en": "open bilibili live protocol. enter client key to connect.",
|
||||
"zh-CN": "新版b站协议,输入身份码"
|
||||
},
|
||||
"plugin.diange.admin": {
|
||||
"en": "Admin",
|
||||
"zh-CN": "管理员"
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type EventId string
|
||||
|
||||
type Event struct {
|
||||
Id EventId
|
||||
Cancelled bool
|
||||
Data interface{}
|
||||
Outdated bool
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
type HandlerFunc func(event *Event)
|
||||
|
||||
type Handler struct {
|
||||
EventId EventId
|
||||
Name string
|
||||
Handler HandlerFunc
|
||||
SkipOutdated bool
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
handlers map[EventId]map[string]*Handler
|
||||
prevEvent map[EventId]*Event
|
||||
queue chan func()
|
||||
stopSig chan int
|
||||
queueSize int
|
||||
workerSize int
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func NewManger(queueSize int, workerSize int) *Manager {
|
||||
manager := &Manager{
|
||||
handlers: make(map[EventId]map[string]*Handler),
|
||||
prevEvent: make(map[EventId]*Event),
|
||||
queue: make(chan func(), queueSize),
|
||||
stopSig: make(chan int, workerSize),
|
||||
queueSize: queueSize,
|
||||
workerSize: workerSize,
|
||||
}
|
||||
for i := 0; i < workerSize; i++ {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-manager.stopSig:
|
||||
return
|
||||
case f := <-manager.queue:
|
||||
f()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
return manager
|
||||
}
|
||||
|
||||
func (h *Manager) NewChildManager() *Manager {
|
||||
return &Manager{
|
||||
handlers: make(map[EventId]map[string]*Handler),
|
||||
prevEvent: make(map[EventId]*Event),
|
||||
queue: h.queue,
|
||||
stopSig: h.stopSig,
|
||||
queueSize: h.queueSize,
|
||||
workerSize: h.workerSize,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Manager) Stop() {
|
||||
for i := 0; i < h.workerSize; i++ {
|
||||
h.stopSig <- 0
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Manager) Register(handler *Handler) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
m, ok := h.handlers[handler.EventId]
|
||||
if !ok {
|
||||
m = make(map[string]*Handler)
|
||||
h.handlers[handler.EventId] = m
|
||||
}
|
||||
m[handler.Name] = handler
|
||||
}
|
||||
|
||||
func (h *Manager) RegisterA(id EventId, name string, handler HandlerFunc) {
|
||||
h.Register(&Handler{
|
||||
EventId: id,
|
||||
Name: name,
|
||||
Handler: handler,
|
||||
SkipOutdated: true,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Manager) UnregisterAll() {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
h.handlers = make(map[EventId]map[string]*Handler)
|
||||
}
|
||||
|
||||
func (h *Manager) Unregister(name string) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
for _, m := range h.handlers {
|
||||
if _, ok := m[name]; ok {
|
||||
delete(m, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Manager) Call(event *Event) {
|
||||
h.lock.Lock()
|
||||
|
||||
handlers, ok := h.handlers[event.Id]
|
||||
if e := h.prevEvent[event.Id]; e != nil {
|
||||
e.lock.Lock()
|
||||
e.Outdated = true
|
||||
e.lock.Unlock()
|
||||
}
|
||||
h.prevEvent[event.Id] = event
|
||||
h.lock.Unlock()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for _, eh := range handlers {
|
||||
eventHandler := eh
|
||||
h.queue <- func() {
|
||||
event.lock.Lock()
|
||||
if eventHandler.SkipOutdated && event.Outdated {
|
||||
event.lock.Unlock()
|
||||
return
|
||||
}
|
||||
eventHandler.Handler(event)
|
||||
event.lock.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Manager) CallA(id EventId, data interface{}) {
|
||||
h.Call(&Event{
|
||||
Id: id,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestEventSeq(t *testing.T) {
|
||||
m := NewManger(128, 16)
|
||||
m.RegisterA("ceshi", "asdf1", func(event *Event) {
|
||||
fmt.Println("Num:", event.Data)
|
||||
})
|
||||
go func() {
|
||||
for i := 0; i < 1000; i++ {
|
||||
m.CallA("ceshi", fmt.Sprintf("a%d", i))
|
||||
}
|
||||
}()
|
||||
for i := 0; i < 1000; i++ {
|
||||
m.CallA("ceshi", i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventWeired(t *testing.T) {
|
||||
m := NewManger(128, 2)
|
||||
m.RegisterA("playlist.update", "asdf1", func(event *Event) {
|
||||
fmt.Printf("%d %p, outdated: %t\n", event.Data, event, event.Outdated)
|
||||
})
|
||||
for i := 0; i < 2; i++ {
|
||||
fmt.Println("asdfsafasfasfasfasfasf")
|
||||
m.CallA("playlist.update", i)
|
||||
fmt.Println("asdfsafasfasfasfasfasf")
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package event
|
||||
|
||||
var MAX_QUEUE_SIZE = 128
|
||||
var MAX_WORKER_SIZE = 16
|
||||
|
||||
var MainManager *Manager
|
||||
|
||||
func init() {
|
||||
MainManager = NewManger(MAX_QUEUE_SIZE, MAX_WORKER_SIZE)
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package logger
|
||||
|
||||
//var Logger *logrus.Logger
|
||||
//
|
||||
//func init() {
|
||||
// Logger = logrus.New()
|
||||
// Logger.SetLevel(config.Log.Level)
|
||||
// Logger.SetFormatter(&nested.Formatter{
|
||||
// FieldsOrder: []string{"Module"},
|
||||
// HideKeys: true,
|
||||
// NoColors: true,
|
||||
// })
|
||||
// //_ = os.Truncate(config.Log.Path, 0)
|
||||
// file, err := os.OpenFile(config.Log.Path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
// if err == nil {
|
||||
// Logger.Out = io.MultiWriter(file, os.Stdout)
|
||||
// } else {
|
||||
// Logger.Info("Failed to log to file, using default stdout")
|
||||
// }
|
||||
// if config.Log.RedirectStderr {
|
||||
// Logger.Info("panic/stderr redirect to log file")
|
||||
// if _, err = paniclog.RedirectStderr(file); err != nil {
|
||||
// Logger.Infof("Failed to redirect stderr to to file: %s", err)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -2,6 +2,7 @@ package util
|
||||
|
||||
import "fmt"
|
||||
|
||||
func FormatTime(time int) string {
|
||||
return fmt.Sprintf("%01d:%02d", time/60, time%60)
|
||||
// FormatTime formats time in seconds to string in format "m:ss"
|
||||
func FormatTime(sec int) string {
|
||||
return fmt.Sprintf("%01d:%02d", sec/60, sec%60)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package util
|
||||
|
||||
func GetWindowHandle(title string) uintptr {
|
||||
return getWindowHandle(title)
|
||||
//return getWindowHandle(title)
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
)
|
||||
|
||||
type LiveClientCtor func(id string, ev *event.Manager, log ILogger) (LiveClient, error)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
)
|
||||
|
||||
type PlayerCtor func(ev *event.Manager, log ILogger) IPlayer
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
)
|
||||
|
||||
type IPlaylist interface {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
)
|
||||
|
||||
// IControlBridge is the interface for all controller and
|
||||
|
||||
@@ -1,54 +1,56 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/core/model"
|
||||
)
|
||||
//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"
|
||||
// EventLyricUpdate event.EventId = "lyric.update"
|
||||
// EventLyricReload event.EventId = "lyric.reload"
|
||||
//)
|
||||
|
||||
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"
|
||||
EventLyricUpdate event.EventId = "lyric.update"
|
||||
EventLyricReload event.EventId = "lyric.reload"
|
||||
)
|
||||
const ErrorUpdate = "update.error"
|
||||
|
||||
func EventPlayerPropertyUpdate(property model.PlayerProperty) event.EventId {
|
||||
return event.EventId("player.property.update." + string(property))
|
||||
type ErrorUpdateEvent struct {
|
||||
Error error
|
||||
}
|
||||
|
||||
type PlaylistInsertEvent struct {
|
||||
Playlist *model.Playlist
|
||||
Index int
|
||||
Media *model.Media
|
||||
}
|
||||
|
||||
type PlaylistUpdateEvent struct {
|
||||
Playlist *model.Playlist // Playlist is a copy of the playlist
|
||||
}
|
||||
|
||||
type PlayEvent struct {
|
||||
Media *model.Media
|
||||
}
|
||||
|
||||
type LyricUpdateEvent struct {
|
||||
Lyrics *model.Lyric
|
||||
Time float64
|
||||
Lyric *model.LyricContext
|
||||
}
|
||||
|
||||
type LyricReloadEvent struct {
|
||||
Lyrics *model.Lyric
|
||||
}
|
||||
|
||||
type PlayerPropertyUpdateEvent struct {
|
||||
Property model.PlayerProperty
|
||||
Value model.PlayerPropertyValue
|
||||
}
|
||||
|
||||
type LiveRoomStatusUpdateEvent struct {
|
||||
RoomTitle string
|
||||
Status bool
|
||||
}
|
||||
//
|
||||
//func EventPlayerPropertyUpdate(property model.PlayerProperty) event.EventId {
|
||||
// return event.EventId("player.property.update." + string(property))
|
||||
//}
|
||||
//
|
||||
//type PlaylistInsertEvent struct {
|
||||
// Playlist *model.Playlist
|
||||
// Index int
|
||||
// Media *model.Media
|
||||
//}
|
||||
//
|
||||
//type PlaylistUpdateEvent struct {
|
||||
// Playlist *model.Playlist // Playlist is a copy of the playlist
|
||||
//}
|
||||
//
|
||||
//type PlayEvent struct {
|
||||
// Media *model.Media
|
||||
//}
|
||||
//
|
||||
//type LyricUpdateEvent struct {
|
||||
// Lyrics *model.Lyric
|
||||
// Time float64
|
||||
// Lyric *model.LyricContext
|
||||
//}
|
||||
//
|
||||
//type LyricReloadEvent struct {
|
||||
// Lyrics *model.Lyric
|
||||
//}
|
||||
//
|
||||
//type PlayerPropertyUpdateEvent struct {
|
||||
// Property model.PlayerProperty
|
||||
// Value model.PlayerPropertyValue
|
||||
//}
|
||||
//
|
||||
//type LiveRoomStatusUpdateEvent struct {
|
||||
// RoomTitle string
|
||||
// Status bool
|
||||
//}
|
||||
|
||||
@@ -1,16 +1,73 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/core/adapter"
|
||||
"AynaLivePlayer/core/model"
|
||||
liveroomsdk "github.com/AynaLivePlayer/liveroom-sdk"
|
||||
)
|
||||
|
||||
const (
|
||||
LiveRoomStatusChange event.EventId = "liveclient.status.change"
|
||||
LiveRoomMessageReceive event.EventId = "liveclient.message.receive"
|
||||
)
|
||||
//const (
|
||||
// LiveRoomStatusChange event.EventId = "liveclient.status.change"
|
||||
// LiveRoomMessageReceive event.EventId = "liveclient.message.receive"
|
||||
//)
|
||||
//
|
||||
//type StatusChangeEvent struct {
|
||||
// Connected bool
|
||||
// Client adapter.LiveClient
|
||||
//}
|
||||
|
||||
type StatusChangeEvent struct {
|
||||
Connected bool
|
||||
Client adapter.LiveClient
|
||||
const LiveRoomAddCmd = "cmd.liveroom.add"
|
||||
|
||||
type LiveRoomAddCmdEvent struct {
|
||||
Title string
|
||||
Provider string
|
||||
RoomKey string
|
||||
}
|
||||
|
||||
const LiveRoomProviderUpdate = "update.liveroom.provider"
|
||||
|
||||
type LiveRoomProviderUpdateEvent struct {
|
||||
Providers []model.LiveRoomProviderInfo
|
||||
}
|
||||
|
||||
const LiveRoomRemoveCmd = "cmd.liveroom.remove"
|
||||
|
||||
type LiveRoomRemoveCmdEvent struct {
|
||||
Identifier string
|
||||
}
|
||||
|
||||
const LiveRoomRoomsUpdate = "update.liveroom.rooms"
|
||||
|
||||
type LiveRoomRoomsUpdateEvent struct {
|
||||
Rooms []model.LiveRoom
|
||||
}
|
||||
|
||||
const LiveRoomStatusUpdate = "update.liveroom.status"
|
||||
|
||||
type LiveRoomStatusUpdateEvent struct {
|
||||
Room model.LiveRoom
|
||||
}
|
||||
|
||||
const LiveRoomConfigChangeCmd = "cmd.liveroom.config.change"
|
||||
|
||||
type LiveRoomConfigChangeCmdEvent struct {
|
||||
Identifier string
|
||||
Config model.LiveRoomConfig
|
||||
}
|
||||
|
||||
const LiveRoomOperationCmd = "cmd.liveroom.operation"
|
||||
|
||||
type LiveRoomOperationCmdEvent struct {
|
||||
Identifier string
|
||||
SetConnect bool // connect or disconnect
|
||||
}
|
||||
|
||||
const LiveRoomOperationFinish = "update.liveroom.operation"
|
||||
|
||||
type LiveRoomOperationFinishEvent struct {
|
||||
}
|
||||
|
||||
const LiveRoomMessageReceive = "update.liveroom.message"
|
||||
|
||||
type LiveRoomMessageReceiveEvent struct {
|
||||
Message *liveroomsdk.Message
|
||||
}
|
||||
|
||||
37
core/events/player_control.go
Normal file
37
core/events/player_control.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/model"
|
||||
)
|
||||
|
||||
const PlayerVolumeChangeCmd = "cmd.player.op.change_volume"
|
||||
|
||||
type PlayerVolumeChangeCmdEvent struct {
|
||||
Volume float64 // Volume from 0-100
|
||||
}
|
||||
|
||||
const PlayerPlayCmd = "cmd.player.op.play"
|
||||
|
||||
type PlayerPlayCmdEvent struct {
|
||||
Media model.Media
|
||||
}
|
||||
|
||||
const PlayerSeekCmd = "cmd.player.op.seek"
|
||||
|
||||
type PlayerSeekCmdEvent struct {
|
||||
Position float64
|
||||
// Absolute is the seek mode.
|
||||
// if absolute = true : position is the time in second
|
||||
// if absolute = false: position is in percentage eg 0.1 0.2
|
||||
Absolute bool
|
||||
}
|
||||
|
||||
const PlayerToggleCmd = "cmd.player.op.toggle"
|
||||
|
||||
type PlayerToggleCmdEvent struct {
|
||||
}
|
||||
|
||||
const PlayerPlayNextCmd = "cmd.player.op.next"
|
||||
|
||||
type PlayerPlayNextCmdEvent struct {
|
||||
}
|
||||
23
core/events/player_lyric.go
Normal file
23
core/events/player_lyric.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package events
|
||||
|
||||
import "github.com/AynaLivePlayer/miaosic"
|
||||
|
||||
const PlayerLyricRequestCmd = "cmd.player.lyric.request"
|
||||
|
||||
type PlayerLyricRequestCmdEvent struct {
|
||||
}
|
||||
|
||||
const PlayerLyricReload = "update.player.lyric.reload"
|
||||
|
||||
type PlayerLyricReloadEvent struct {
|
||||
Lyrics miaosic.Lyrics
|
||||
}
|
||||
|
||||
const PlayerLyricPosUpdate = "update.player.lyric.pos"
|
||||
|
||||
type PlayerLyricPosUpdateEvent struct {
|
||||
Time float64
|
||||
CurrentIndex int // -1 means no lyric
|
||||
CurrentLine miaosic.LyricLine
|
||||
Total int // total lyric count
|
||||
}
|
||||
46
core/events/player_property.go
Normal file
46
core/events/player_property.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package events
|
||||
|
||||
import "AynaLivePlayer/core/model"
|
||||
|
||||
const PlayerPlayingUpdate = "update.player.playing"
|
||||
|
||||
type PlayerPlayingUpdateEvent struct {
|
||||
Media model.Media
|
||||
Removed bool // if no media is playing, removed is true
|
||||
}
|
||||
|
||||
const PlayerPropertyPauseUpdate = "update.player.property.pause"
|
||||
|
||||
type PlayerPropertyPauseUpdateEvent struct {
|
||||
Paused bool
|
||||
}
|
||||
|
||||
const PlayerPropertyPercentPosUpdate = "update.player.property.percent_pos"
|
||||
|
||||
type PlayerPropertyPercentPosUpdateEvent struct {
|
||||
PercentPos float64
|
||||
}
|
||||
|
||||
const PlayerPropertyIdleActiveUpdate = "update.player.property.idle_active"
|
||||
|
||||
type PlayerPropertyIdleActiveUpdateEvent struct {
|
||||
IsIdle bool
|
||||
}
|
||||
|
||||
const PlayerPropertyTimePosUpdate = "update.player.property.time_pos"
|
||||
|
||||
type PlayerPropertyTimePosUpdateEvent struct {
|
||||
TimePos float64 // Time in seconds
|
||||
}
|
||||
|
||||
const PlayerPropertyDurationUpdate = "update.player.property.duration"
|
||||
|
||||
type PlayerPropertyDurationUpdateEvent struct {
|
||||
Duration float64 // Duration in seconds
|
||||
}
|
||||
|
||||
const PlayerPropertyVolumeUpdate = "update.player.property.volume"
|
||||
|
||||
type PlayerPropertyVolumeUpdateEvent struct {
|
||||
Volume float64 // Volume from 0-100
|
||||
}
|
||||
7
core/events/player_videoplayer.go
Normal file
7
core/events/player_videoplayer.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package events
|
||||
|
||||
const PlayerVideoPlayerSetWindowHandleCmd = "cmd.player.videoplayer.set_window_handle"
|
||||
|
||||
type PlayerVideoPlayerSetWindowHandleCmdEvent struct {
|
||||
Handle uintptr
|
||||
}
|
||||
81
core/events/playlist.go
Normal file
81
core/events/playlist.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
)
|
||||
|
||||
func PlaylistDetailUpdate(id model.PlaylistID) event.EventId {
|
||||
return event.EventId("update.playlist.detail." + id)
|
||||
}
|
||||
|
||||
type PlaylistDetailUpdateEvent struct {
|
||||
Medias []model.Media
|
||||
}
|
||||
|
||||
func PlaylistMoveCmd(id model.PlaylistID) event.EventId {
|
||||
return event.EventId("cmd.playlist.move." + id)
|
||||
}
|
||||
|
||||
type PlaylistMoveCmdEvent struct {
|
||||
From int
|
||||
To int
|
||||
}
|
||||
|
||||
func PlaylistDeleteCmd(id model.PlaylistID) event.EventId {
|
||||
return event.EventId("cmd.playlist.delete." + id)
|
||||
}
|
||||
|
||||
type PlaylistDeleteCmdEvent struct {
|
||||
Index int
|
||||
}
|
||||
|
||||
func PlaylistInsertCmd(id model.PlaylistID) event.EventId {
|
||||
return event.EventId("cmd.playlist.insert." + id)
|
||||
}
|
||||
|
||||
type PlaylistInsertCmdEvent struct {
|
||||
Position int // position to insert, -1 means last one
|
||||
Media model.Media
|
||||
}
|
||||
|
||||
func PlaylistInsertUpdate(id model.PlaylistID) event.EventId {
|
||||
return event.EventId("update.playlist.insert." + id)
|
||||
}
|
||||
|
||||
type PlaylistInsertUpdateEvent struct {
|
||||
Position int // position to insert, -1 means last one
|
||||
Media model.Media
|
||||
}
|
||||
|
||||
func PlaylistNextCmd(id model.PlaylistID) event.EventId {
|
||||
return event.EventId("cmd.playlist.next." + id)
|
||||
}
|
||||
|
||||
type PlaylistNextCmdEvent struct {
|
||||
Remove bool // remove the media after next
|
||||
}
|
||||
|
||||
func PlaylistNextUpdate(id model.PlaylistID) event.EventId {
|
||||
return event.EventId("update.playlist.next." + id)
|
||||
}
|
||||
|
||||
type PlaylistNextUpdateEvent struct {
|
||||
Media model.Media
|
||||
}
|
||||
|
||||
func PlaylistModeChangeCmd(id model.PlaylistID) event.EventId {
|
||||
return event.EventId("cmd.playlist.mode." + id)
|
||||
}
|
||||
|
||||
type PlaylistModeChangeCmdEvent struct {
|
||||
Mode model.PlaylistMode
|
||||
}
|
||||
|
||||
func PlaylistModeChangeUpdate(id model.PlaylistID) event.EventId {
|
||||
return event.EventId("update.playlist.mode." + id)
|
||||
}
|
||||
|
||||
type PlaylistModeChangeUpdateEvent struct {
|
||||
Mode model.PlaylistMode
|
||||
}
|
||||
24
core/events/search.go
Normal file
24
core/events/search.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/model"
|
||||
)
|
||||
|
||||
const SearchCmd = "cmd.search"
|
||||
|
||||
type SearchCmdEvent struct {
|
||||
Keyword string
|
||||
Provider string
|
||||
}
|
||||
|
||||
const SearchResultUpdate = "update.search_result"
|
||||
|
||||
type SearchResultUpdateEvent struct {
|
||||
Medias []model.Media
|
||||
}
|
||||
|
||||
const SearchProviderUpdate = "update.search.provider.update"
|
||||
|
||||
type SearchProviderUpdateEvent struct {
|
||||
Providers []string
|
||||
}
|
||||
@@ -1,40 +1,26 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
import "github.com/AynaLivePlayer/liveroom-sdk"
|
||||
|
||||
type LiveRoomConfig struct {
|
||||
AutoConnect bool `json:"auto_connect"`
|
||||
}
|
||||
|
||||
type LiveRoom struct {
|
||||
ClientName string
|
||||
ID string
|
||||
Title string
|
||||
AutoConnect bool
|
||||
AutoReconnect bool
|
||||
LiveRoom liveroom.LiveRoom `json:"live_room"`
|
||||
Config LiveRoomConfig `json:"config"`
|
||||
Title string `json:"title"`
|
||||
Status bool `json:"-"`
|
||||
}
|
||||
|
||||
func (r *LiveRoom) String() string {
|
||||
return fmt.Sprintf("<LiveRooms %s:%s>", r.ClientName, r.ID)
|
||||
func (r *LiveRoom) DisplayName() string {
|
||||
if r.Title != "" {
|
||||
return r.Title
|
||||
}
|
||||
return r.LiveRoom.Identifier()
|
||||
}
|
||||
|
||||
func (r *LiveRoom) Identifier() string {
|
||||
return fmt.Sprintf("%s_%s", r.ClientName, r.ID)
|
||||
}
|
||||
|
||||
type UserMedal struct {
|
||||
Name string
|
||||
Level int
|
||||
RoomID string
|
||||
}
|
||||
|
||||
type DanmuUser struct {
|
||||
Uid string
|
||||
Username string
|
||||
Medal UserMedal
|
||||
Admin bool
|
||||
Privilege int
|
||||
}
|
||||
|
||||
type DanmuMessage struct {
|
||||
User DanmuUser
|
||||
Message string
|
||||
type LiveRoomProviderInfo struct {
|
||||
Name string
|
||||
Description string
|
||||
}
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/spf13/cast"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var timeTagRegex = regexp.MustCompile("\\[[0-9]+:[0-9]+(\\.[0-9]+)?\\]")
|
||||
|
||||
type LyricLine struct {
|
||||
Time float64 // in seconds
|
||||
Lyric string
|
||||
Translation string
|
||||
}
|
||||
|
||||
type LyricContext struct {
|
||||
Now *LyricLine
|
||||
Index int
|
||||
Total int
|
||||
Prev []*LyricLine
|
||||
Next []*LyricLine
|
||||
}
|
||||
|
||||
type Lyric struct {
|
||||
Lyrics []*LyricLine
|
||||
}
|
||||
|
||||
func LoadLyric(lyric string) *Lyric {
|
||||
tmp := make(map[float64]*LyricLine)
|
||||
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])
|
||||
times = append(times, t)
|
||||
tmp[t] = &LyricLine{
|
||||
Time: t,
|
||||
Lyric: lrc,
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Float64s(times)
|
||||
lrcs := make([]*LyricLine, len(times))
|
||||
for index, time := range times {
|
||||
lrcs[index] = tmp[time]
|
||||
}
|
||||
if len(lrcs) == 0 {
|
||||
lrcs = append(lrcs, &LyricLine{Time: 0, Lyric: ""})
|
||||
}
|
||||
lrcs = append(lrcs, &LyricLine{
|
||||
Time: 99999999999,
|
||||
Lyric: "",
|
||||
})
|
||||
return &Lyric{Lyrics: lrcs}
|
||||
}
|
||||
|
||||
func (l *Lyric) findIndexV1(time float64) int {
|
||||
for i := 0; i < len(l.Lyrics)-1; i++ {
|
||||
if l.Lyrics[i].Time <= time && time < l.Lyrics[i+1].Time {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (l *Lyric) findIndex(time float64) int {
|
||||
start := 0
|
||||
end := len(l.Lyrics) - 1
|
||||
mid := (start + end) / 2
|
||||
for start < end {
|
||||
if l.Lyrics[mid].Time <= time && time < l.Lyrics[mid+1].Time {
|
||||
return mid
|
||||
}
|
||||
if l.Lyrics[mid].Time > time {
|
||||
end = mid
|
||||
} else {
|
||||
start = mid
|
||||
}
|
||||
mid = (start + end) / 2
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (l *Lyric) Find(time float64) *LyricLine {
|
||||
idx := l.findIndex(time)
|
||||
if idx == -1 {
|
||||
return nil
|
||||
}
|
||||
return l.Lyrics[idx]
|
||||
}
|
||||
|
||||
func (l *Lyric) FindContext(time float64, prev int, next int) *LyricContext {
|
||||
prev = -prev
|
||||
idx := l.findIndex(time)
|
||||
if idx == -1 {
|
||||
return nil
|
||||
}
|
||||
if (idx + prev) < 0 {
|
||||
prev = -idx
|
||||
}
|
||||
if (idx + 1 + next) > len(l.Lyrics) {
|
||||
next = len(l.Lyrics) - idx - 1
|
||||
}
|
||||
return &LyricContext{
|
||||
Now: l.Lyrics[idx],
|
||||
Index: idx,
|
||||
Total: len(l.Lyrics),
|
||||
Prev: l.Lyrics[idx+prev : idx],
|
||||
Next: l.Lyrics[idx+1 : idx+1+next],
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/magiconair/properties/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testLyric = "[ti:双截棍]\n[ar:周杰伦]\n[al:范特西]\n[00:03.85]双截棍\n[00:07.14]\n[00:30.13]岩烧店的烟味弥漫隔壁是国术馆\n[00:32.57]店里面的妈妈桑茶道有三段\n[00:34.61]教拳脚武术的老板练铁沙掌耍杨家枪\n[00:37.34]硬底子功夫最擅长还会金钟罩铁步衫\n[00:39.67]他们儿子我习惯从小就耳濡目染\n[00:41.96]什么刀枪跟棍棒我都耍的有模有样\n[00:44.22]什么兵器最喜欢双截棍柔中带刚\n[00:46.73]想要去河南嵩山学少林跟武当\n[00:49.24]干什么(客)干什么(客)呼吸吐纳心自在\n[00:51.28]干什么(客)干什么(客)气沉丹田手心开\n[00:53.44]干什么(客)干什么(客)日行千里系沙袋\n[00:56.13]飞檐走壁莫奇怪去去就来\n[00:58.35]一个马步向前一记左钩拳右钩拳\n[01:01.26]一句惹毛我的人有危险一再重演\n[01:04.02]一根我不抽的菸一放好多年它一直在身边\n[01:07.28]干什么(客)干什么(客)我打开任督二脉\n[01:10.27]干什么(客)干什么(客)东亚病夫的招牌\n[01:12.75]干什么(客)干什么(客)已被我一脚踢开\n[02:32.62][01:54.69][01:15.40]快使用双截棍哼哼哈兮\n[02:34.52][01:56.63][01:18.40]快使用双截棍哼哼哈兮\n[02:36.88][01:58.98][01:20.71]习武之人切记仁者无敌\n[02:39.45][02:01.66][01:23.27]是谁在练太极风生水起\n[02:41.97][02:03.93][01:25.74]快使用双截棍哼哼哈兮\n[02:44.42][02:06.11][01:27.75]快使用双截棍哼哼哈兮\n[02:47.01][02:08.54][01:30.13]如果我有轻功飞檐走壁\n[02:49.36][02:11.03][01:32.67]为人耿直不屈一身正气\n[02:53.81]快使用双截棍哼\n[02:56.30]我用手刀防御哼\n[02:58.52]漂亮的回旋踢\n[02:59.52]"
|
||||
|
||||
func TestLyric(t *testing.T) {
|
||||
lryic := LoadLyric(testLyric)
|
||||
for _, lrc := range lryic.Lyrics {
|
||||
fmt.Println(lrc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLyricFind(t *testing.T) {
|
||||
lryic := LoadLyric(testLyric)
|
||||
fmt.Println(lryic.Find(90.4))
|
||||
for _, l := range lryic.FindContext(90.4, -2, 2).Next {
|
||||
fmt.Println(l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLyricFindV2(t *testing.T) {
|
||||
lryic := LoadLyric(testLyric)
|
||||
for i := 0.0; i < 170; i += 0.01 {
|
||||
assert.Equal(t, lryic.Find(i), lryic.Find(i))
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,38 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/AynaLivePlayer/liveroom-sdk"
|
||||
"github.com/AynaLivePlayer/miaosic"
|
||||
)
|
||||
|
||||
type Picture struct {
|
||||
Url string
|
||||
Data []byte
|
||||
type User struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (p Picture) Exists() bool {
|
||||
return p.Url != "" || p.Data != nil
|
||||
}
|
||||
var PlaylistUser = User{Name: "Playlists"}
|
||||
var SystemUser = User{Name: "System"}
|
||||
var HistoryUser = User{Name: "History"}
|
||||
|
||||
type Media struct {
|
||||
Title string
|
||||
Artist string
|
||||
Cover Picture
|
||||
Album string
|
||||
Lyric string
|
||||
Url string
|
||||
Header map[string]string
|
||||
User interface{}
|
||||
Meta interface{}
|
||||
Info miaosic.MediaInfo
|
||||
User interface{}
|
||||
}
|
||||
|
||||
func (m *Media) ToUser() *User {
|
||||
if u, ok := m.User.(*User); ok {
|
||||
func (m *Media) IsLiveRoomUser() bool {
|
||||
_, ok := m.User.(liveroom.User)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (m *Media) ToUser() User {
|
||||
if u, ok := m.User.(User); ok {
|
||||
return u
|
||||
}
|
||||
return &User{Name: m.DanmuUser().Username}
|
||||
return User{Name: m.DanmuUser().Username}
|
||||
}
|
||||
|
||||
func (m *Media) DanmuUser() *DanmuUser {
|
||||
if u, ok := m.User.(*DanmuUser); ok {
|
||||
func (m *Media) DanmuUser() liveroom.User {
|
||||
if u, ok := m.User.(liveroom.User); ok {
|
||||
return u
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Media) Copy() *Media {
|
||||
newMedia := &Media{}
|
||||
copier.Copy(newMedia, m)
|
||||
return newMedia
|
||||
return liveroom.User{}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type A struct {
|
||||
A string
|
||||
}
|
||||
|
||||
type B struct {
|
||||
B string
|
||||
}
|
||||
|
||||
func TestStruct(t *testing.T) {
|
||||
var x interface{} = &A{A: "123"}
|
||||
y, ok := x.(*A)
|
||||
fmt.Println(y, ok)
|
||||
z, ok := x.(*B)
|
||||
fmt.Println(z, ok)
|
||||
}
|
||||
|
||||
func TestMedia_Copy(t *testing.T) {
|
||||
m := &Media{Title: "asdf", User: &User{Name: "123"}}
|
||||
m2 := m.Copy()
|
||||
fmt.Println(m, m2)
|
||||
m2.User.(*User).Name = "456"
|
||||
fmt.Println(m.User.(*User).Name, m2)
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package model
|
||||
|
||||
import "fmt"
|
||||
|
||||
type PlaylistMode int
|
||||
|
||||
const (
|
||||
@@ -10,28 +8,11 @@ const (
|
||||
PlaylistModeRepeat
|
||||
)
|
||||
|
||||
type Playlist struct {
|
||||
Title string // can be same, display name
|
||||
Medias []*Media
|
||||
Mode PlaylistMode
|
||||
Meta Meta
|
||||
}
|
||||
type PlaylistID string
|
||||
|
||||
func (p Playlist) String() string {
|
||||
return fmt.Sprintf("<Playlist %s len:%d>", p.Title, len(p.Medias))
|
||||
}
|
||||
|
||||
func (p *Playlist) Size() int {
|
||||
return len(p.Medias)
|
||||
}
|
||||
|
||||
func (p *Playlist) Copy() *Playlist {
|
||||
medias := make([]*Media, len(p.Medias))
|
||||
copy(medias, p.Medias)
|
||||
return &Playlist{
|
||||
Title: p.Title,
|
||||
Medias: medias,
|
||||
Mode: p.Mode,
|
||||
Meta: p.Meta,
|
||||
}
|
||||
}
|
||||
const (
|
||||
PlaylistIDPlayer PlaylistID = "player"
|
||||
PlaylistIDSystem PlaylistID = "system"
|
||||
PlaylistIDHistory PlaylistID = "history"
|
||||
PlaylistIDPlaylists PlaylistID = "playlists"
|
||||
)
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package model
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Meta struct {
|
||||
Name string
|
||||
Id string
|
||||
}
|
||||
|
||||
func (m Meta) String() string {
|
||||
return fmt.Sprintf("<Meta %s:%s>", m.Name, m.Id)
|
||||
}
|
||||
|
||||
func (m Meta) Identifier() string {
|
||||
return fmt.Sprintf("%s_%s", m.Name, m.Id)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package model
|
||||
|
||||
type User struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func ApplyUser(medias []*Media, user interface{}) {
|
||||
for _, m := range medias {
|
||||
m.User = user
|
||||
}
|
||||
}
|
||||
|
||||
func ToSpMedia(media *Media, user *User) *Media {
|
||||
media = media.Copy()
|
||||
media.User = user
|
||||
return media
|
||||
}
|
||||
10
global/global.go
Normal file
10
global/global.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/logger"
|
||||
)
|
||||
|
||||
var Logger logger.ILogger = nil
|
||||
|
||||
var EventManager *event.Manager = nil
|
||||
83
go.mod
83
go.mod
@@ -2,55 +2,70 @@ module AynaLivePlayer
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
fyne.io/fyne/v2 v2.2.4
|
||||
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
|
||||
github.com/dhowden/tag v0.0.0-20220618230019-adf36e896086
|
||||
github.com/go-resty/resty/v2 v2.7.0
|
||||
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.8.0
|
||||
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
|
||||
golang.org/x/sys v0.1.0
|
||||
gopkg.in/ini.v1 v1.66.4
|
||||
replace (
|
||||
github.com/AynaLivePlayer/liveroom-sdk v0.1.0 => ./pkg/liveroom-sdk // submodule
|
||||
github.com/AynaLivePlayer/miaosic v0.1.5 => ./pkg/miaosic // submodule
|
||||
)
|
||||
|
||||
require (
|
||||
fyne.io/systray v1.10.1-0.20230207085535-4a244dbb9d03 // indirect
|
||||
fyne.io/fyne/v2 v2.4.4
|
||||
fyne.io/x/fyne v0.0.0-20240326131024-3ba9170cc3be
|
||||
github.com/AynaLivePlayer/liveroom-sdk v0.1.0
|
||||
github.com/AynaLivePlayer/miaosic v0.1.5
|
||||
github.com/XiaoMengXinX/Music163Api-Go v0.1.30
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.1
|
||||
github.com/aynakeya/go-mpv v0.0.6
|
||||
github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b
|
||||
github.com/go-resty/resty/v2 v2.7.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/jinzhu/copier v0.4.0
|
||||
github.com/mattn/go-colorable v0.1.12
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/tidwall/gjson v1.16.0
|
||||
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459
|
||||
go.uber.org/zap v1.26.0
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
|
||||
golang.org/x/sys v0.13.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
)
|
||||
|
||||
require (
|
||||
fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e // indirect
|
||||
github.com/PuerkitoBio/goquery v1.7.1 // indirect
|
||||
github.com/andybalholm/cascadia v1.2.0 // indirect
|
||||
github.com/aynakeya/deepcolor v1.0.2 // indirect
|
||||
github.com/aynakeya/open-bilibili-live v0.0.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fredbi/uri v0.1.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/fredbi/uri v1.0.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect
|
||||
github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 // indirect
|
||||
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect
|
||||
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect
|
||||
github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8 // indirect
|
||||
github.com/go-text/typesetting v0.1.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/google/uuid v1.5.0 // indirect
|
||||
github.com/gopherjs/gopherjs v1.17.2 // indirect
|
||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76 // indirect
|
||||
github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // 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
|
||||
github.com/yuin/goldmark v1.4.13 // indirect
|
||||
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/text v0.3.7 // indirect
|
||||
github.com/yuin/goldmark v1.5.5 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/image v0.11.0 // indirect
|
||||
golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 // indirect
|
||||
)
|
||||
|
||||
173
go.sum
173
go.sum
@@ -37,27 +37,32 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
fyne.io/fyne/v2 v2.2.4 h1:izyiDUjJYAB7B/MST7M9GDs+mQ0CwDgRZTiVJZQoEe4=
|
||||
fyne.io/fyne/v2 v2.2.4/go.mod h1:MBoGuHzLLSXdQOWFAwWhIhYTEMp33zqtGCReSWhaQTA=
|
||||
fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE=
|
||||
fyne.io/systray v1.10.1-0.20230207085535-4a244dbb9d03 h1:zb84y6X6lIi4Aeo8ehu6Qtu2fipBZ6Wzp41Ki/F4qdg=
|
||||
fyne.io/systray v1.10.1-0.20230207085535-4a244dbb9d03/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE=
|
||||
fyne.io/fyne/v2 v2.4.4 h1:4efSRpoikcGbqQN83yzC9WmF8UNq9olsaJQ/Ejme6Z8=
|
||||
fyne.io/fyne/v2 v2.4.4/go.mod h1:VyrxAOZ3NRZRWBvNIJbfqoKOG4DdbewoPk7ozqJKNPY=
|
||||
fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e h1:Hvs+kW2VwCzNToF3FmnIAzmivNgrclwPgoUdVSrjkP8=
|
||||
fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE=
|
||||
fyne.io/x/fyne v0.0.0-20240326131024-3ba9170cc3be h1:HBAwKsmfTO4r1Ndlksy5oXIxgWiazj0gu2qmhOmkRFU=
|
||||
fyne.io/x/fyne v0.0.0-20240326131024-3ba9170cc3be/go.mod h1:1pa3ZVIopRWNvfSG4ZrSkcZ3mJ8qoHPZv4PT8/zpn1o=
|
||||
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.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/PuerkitoBio/goquery v1.7.1 h1:oE+T06D+1T7LNrn91B4aERsRIeCLJ/oPSa6xB9FPnz4=
|
||||
github.com/PuerkitoBio/goquery v1.7.1/go.mod h1:XY0pP4kfraEmmV1O7Uf6XyjoslwsneBbgeDjLYuN8xY=
|
||||
github.com/XiaoMengXinX/Music163Api-Go v0.1.30 h1:MqRItDFtX1J0JTlFtwN2RwjsYMA7/g/+cTjcOJXy19s=
|
||||
github.com/XiaoMengXinX/Music163Api-Go v0.1.30/go.mod h1:kLU/CkLxKnEJFCge0URvQ0lHt6ImoG1/2aVeNbgV2RQ=
|
||||
github.com/andybalholm/cascadia v1.2.0 h1:vuRCkM5Ozh/BfmsaTm26kbjm0mIOM3yS5Ek/F5h18aE=
|
||||
github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY=
|
||||
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=
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aynakeya/blivedm v0.1.6 h1:fnjyyHYnXAArcLqMRNLQCAdvRFiEVGA/a/54UatZF0k=
|
||||
github.com/aynakeya/blivedm v0.1.6/go.mod h1:g7cA6/BfDcrsD4v9w+P6B9Z+gANi4jPlGkZ1Oyuu/i4=
|
||||
github.com/aynakeya/deepcolor v1.0.2 h1:ldmVMweZTX3uLFpRQBtNyapGMXZ+UqkDp2S08ROD0rE=
|
||||
github.com/aynakeya/deepcolor v1.0.2/go.mod h1:9wdFsi0G4uAQlu58B2/eHBlGoQ8VkmSyPsK+bDZ+6dQ=
|
||||
github.com/aynakeya/go-mpv v0.0.6 h1:WCBwHrzl700C1J3f+aXR+URw/OKYPjwUjDW9diOsXYY=
|
||||
github.com/aynakeya/go-mpv v0.0.6/go.mod h1:do6ImaEyt9dlQ7JRS/8ke+P9q4kGW8+Bf6j3faBQOfE=
|
||||
github.com/aynakeya/open-bilibili-live v0.0.5 h1:SJX+MA7nn3R30+zWJzpUH3E2SrDcDA8C6ZE2QhzT1U0=
|
||||
github.com/aynakeya/open-bilibili-live v0.0.5/go.mod h1:8aYl0767J4wgBkJ6kgOpOuj6FcFFL8GN3HRHGGjYpSk=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
@@ -71,12 +76,11 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
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=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dhowden/tag v0.0.0-20220618230019-adf36e896086 h1:ORubSQoKnncsBnR4zD9CuYFJCPOCuSNEpWEZrDdBXkc=
|
||||
github.com/dhowden/tag v0.0.0-20220618230019-adf36e896086/go.mod h1:Z3Lomva4pyMWYezjMAU5QWRh0p1VvO4199OHlFnyKkM=
|
||||
github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25 h1:simG0vMYFvNriGhaaat7QVVkaVkXzvqcohaBoLZl9Hg=
|
||||
github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25/go.mod h1:Z3Lomva4pyMWYezjMAU5QWRh0p1VvO4199OHlFnyKkM=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
@@ -85,13 +89,12 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8=
|
||||
github.com/fredbi/uri v0.1.0 h1:8XBBD74STBLcWJ5smjEkKCZivSxSKMhFB0FbQUKeNyM=
|
||||
github.com/fredbi/uri v0.1.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/fredbi/uri v1.0.0 h1:s4QwUAZ8fz+mbTsukND+4V5f+mJ/wjaTokwstGUAemg=
|
||||
github.com/fredbi/uri v1.0.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe h1:A/wiwvQ0CAjPkuJytaD+SsXkPU0asQ+guQEIg1BJGX4=
|
||||
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg=
|
||||
github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 h1:+31CdF/okdokeFNoy9L/2PccG3JFidQT3ev64/r4pYU=
|
||||
@@ -107,17 +110,17 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
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=
|
||||
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
|
||||
github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8 h1:VkKnvzbvHqgEfm351rfr8Uclu5fnwq8HP2ximUzJsBM=
|
||||
github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8/go.mod h1:h29xCucjNsDcYb7+0rJokxVwYAq+9kQ19WiFuBKkYtc=
|
||||
github.com/go-text/typesetting v0.1.0 h1:vioSaLPYcHwPEPLT7gsjCGDCoYSbljxoHJzMnKwVvHw=
|
||||
github.com/go-text/typesetting v0.1.0/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04 h1:zBx+p/W2aQYtNuyZNcTfinWvXBQwYtDfme051PR/lAY=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
||||
github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c h1:JGCm/+tJ9gC6THUxooTldS+CUDsba0qvkvU3DHklqW8=
|
||||
github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@@ -161,7 +164,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.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
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=
|
||||
@@ -179,15 +182,14 @@ github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLe
|
||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20211219123610-ec9572f70e60/go.mod h1:cz9oNYuRUWGdHmLF2IodMLkAhcPtXeULvcBNagUrxTI=
|
||||
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/goxjs/gl v0.0.0-20210104184919-e3fafc6f8f2a/go.mod h1:dy/f2gjY09hwVfIyATps4G2ai7/hLwLkc5TrPqONuXY=
|
||||
@@ -216,10 +218,8 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI=
|
||||
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
|
||||
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
|
||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
@@ -230,16 +230,18 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
|
||||
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
@@ -256,8 +258,6 @@ github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJE
|
||||
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
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/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -268,35 +268,32 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
||||
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
||||
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
|
||||
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
|
||||
github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76 h1:Ga2uagHhDeGysCixLAzH0mS2TU+CrbQavmsHUNkEEVA=
|
||||
github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
|
||||
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
|
||||
github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 h1:oDMiXaTMyBEuZMU53atpxqYsSB3U1CHkeAu2zr6wTeY=
|
||||
github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
@@ -306,23 +303,19 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
|
||||
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
|
||||
github.com/tidwall/gjson v1.8.0/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
|
||||
github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
|
||||
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/gjson v1.16.0 h1:SyXa+dsSPpUlcwEDuKuEBJEz5vzTvOea+9rjyYodQFg=
|
||||
github.com/tidwall/gjson v1.16.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/urfave/cli/v2 v2.4.0/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg=
|
||||
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459 h1:x9pIfbdIjnw+Ylb2vE27Gtqb7BDmfR+nLcJwvbJh98U=
|
||||
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459/go.mod h1:nFvuG3SWu3VWqobG3cX8nt57wXU0OOFapeCs/8axIuM=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@@ -331,8 +324,9 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.5.5 h1:IJznPe8wOzfIKETmMkd06F8nXkmlhaHqFRM9l1hAGsU=
|
||||
github.com/yuin/goldmark v1.5.5/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
|
||||
@@ -344,8 +338,13 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@@ -354,6 +353,7 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -365,12 +365,12 @@ 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/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
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=
|
||||
golang.org/x/image v0.0.0-20220601225756-64ec528b34cd/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
||||
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
|
||||
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -385,8 +385,9 @@ golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPI
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee h1:/tShaw8UTf0XzI8DOZwQHzC7d6Vi3EtrBnftiZ4vAvU=
|
||||
golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
|
||||
golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda h1:O+EUvnBNPwI4eLthn8W5K+cS8zQZfgTABPLNm6Bna34=
|
||||
golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
@@ -396,6 +397,9 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -432,10 +436,13 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b h1:tvrvnPFcdzp294diPnrdZZZ8XUt2Tyj7svb7X52iDuU=
|
||||
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -459,6 +466,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -470,10 +479,8 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -506,10 +513,18 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
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=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -518,8 +533,11 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -576,6 +594,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
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=
|
||||
@@ -683,13 +703,12 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
|
||||
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/config"
|
||||
"AynaLivePlayer/common/i18n"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/gui/component"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type bascicConfig struct {
|
||||
panel fyne.CanvasObject
|
||||
}
|
||||
|
||||
func (b *bascicConfig) Title() string {
|
||||
return i18n.T("gui.config.basic.title")
|
||||
}
|
||||
|
||||
func (b *bascicConfig) Description() string {
|
||||
return i18n.T("gui.config.basic.description")
|
||||
}
|
||||
|
||||
func (b *bascicConfig) CreatePanel() fyne.CanvasObject {
|
||||
if b.panel != nil {
|
||||
return b.panel
|
||||
}
|
||||
|
||||
randomPlaylist := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("gui.config.basic.random_playlist")),
|
||||
newCheckInit(
|
||||
i18n.T("gui.config.basic.random_playlist.user"),
|
||||
func(b bool) {
|
||||
l().Infof("Set random playlist for user: %t", b)
|
||||
if b {
|
||||
API.Playlists().GetCurrent().Model().Mode = model.PlaylistModeRandom
|
||||
} else {
|
||||
API.Playlists().GetCurrent().Model().Mode = model.PlaylistModeNormal
|
||||
}
|
||||
},
|
||||
API.Playlists().GetCurrent().Model().Mode == model.PlaylistModeRandom),
|
||||
newCheckInit(
|
||||
i18n.T("gui.config.basic.random_playlist.system"),
|
||||
func(b bool) {
|
||||
l().Infof("Set random playlist for system: %t", b)
|
||||
if b {
|
||||
API.Playlists().GetDefault().Model().Mode = model.PlaylistModeRandom
|
||||
} else {
|
||||
API.Playlists().GetDefault().Model().Mode = model.PlaylistModeNormal
|
||||
}
|
||||
},
|
||||
API.Playlists().GetDefault().Model().Mode == model.PlaylistModeRandom),
|
||||
)
|
||||
devices := API.PlayControl().GetAudioDevices()
|
||||
deviceDesc := make([]string, len(devices))
|
||||
deviceDesc2Name := make(map[string]string)
|
||||
for i, device := range devices {
|
||||
deviceDesc[i] = device.Description
|
||||
deviceDesc2Name[device.Description] = device.Name
|
||||
}
|
||||
deviceSel := widget.NewSelect(deviceDesc, func(s string) {
|
||||
API.PlayControl().SetAudioDevice(deviceDesc2Name[s])
|
||||
})
|
||||
deviceSel.Selected = API.PlayControl().GetCurrentAudioDevice()
|
||||
outputDevice := container.NewBorder(nil, nil,
|
||||
widget.NewLabel(i18n.T("gui.config.basic.audio_device")), nil,
|
||||
deviceSel)
|
||||
skipPlaylist := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("gui.config.basic.skip_playlist")),
|
||||
component.NewCheckOneWayBinding(
|
||||
i18n.T("gui.config.basic.skip_playlist.prompt"),
|
||||
&API.PlayControl().Config().SkipPlaylist,
|
||||
API.PlayControl().Config().SkipPlaylist),
|
||||
)
|
||||
skipWhenErr := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("gui.config.basic.skip_when_error")),
|
||||
component.NewCheckOneWayBinding(
|
||||
i18n.T("gui.config.basic.skip_when_error.prompt"),
|
||||
&API.PlayControl().Config().AutoNextWhenFail,
|
||||
API.PlayControl().Config().AutoNextWhenFail),
|
||||
)
|
||||
checkUpdateBox := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("gui.config.basic.auto_check_update")),
|
||||
component.NewCheckOneWayBinding(
|
||||
i18n.T("gui.config.basic.auto_check_update.prompt"),
|
||||
&config.General.AutoCheckUpdate,
|
||||
config.General.AutoCheckUpdate),
|
||||
)
|
||||
checkUpdateBtn := widget.NewButton(i18n.T("gui.config.basic.check_update"), func() {
|
||||
err := API.App().CheckUpdate()
|
||||
if err != nil {
|
||||
showDialogIfError(err)
|
||||
return
|
||||
}
|
||||
if API.App().LatestVersion().Version > API.App().Version().Version {
|
||||
dialog.ShowCustom(
|
||||
i18n.T("gui.update.new_version"),
|
||||
"OK",
|
||||
widget.NewRichTextFromMarkdown(API.App().LatestVersion().Info),
|
||||
MainWindow)
|
||||
}
|
||||
})
|
||||
b.panel = container.NewVBox(randomPlaylist, outputDevice, skipPlaylist, skipWhenErr, checkUpdateBox, checkUpdateBtn)
|
||||
return b.panel
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/gui/component"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
func createConfigLayout() fyne.CanvasObject {
|
||||
// initialize config panels
|
||||
for _, c := range ConfigList {
|
||||
c.CreatePanel()
|
||||
}
|
||||
content := container.NewMax()
|
||||
entryList := widget.NewList(
|
||||
func() int {
|
||||
return len(ConfigList)
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
return widget.NewLabel("")
|
||||
},
|
||||
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
|
||||
}
|
||||
}
|
||||
content.Objects = []fyne.CanvasObject{
|
||||
container.NewVScroll(container.NewVBox(desc, widget.NewSeparator(), ConfigList[id].CreatePanel())),
|
||||
}
|
||||
content.Refresh()
|
||||
}
|
||||
|
||||
return component.NewFixedSplitContainer(entryList, content, true, 0.23)
|
||||
}
|
||||
123
gui/gui.go
123
gui/gui.go
@@ -1,42 +1,42 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/config"
|
||||
"AynaLivePlayer/common/i18n"
|
||||
"AynaLivePlayer/common/util"
|
||||
"AynaLivePlayer/core/adapter"
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
config2 "AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"AynaLivePlayer/resource"
|
||||
"fmt"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/app"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/driver/desktop"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
|
||||
_logger "AynaLivePlayer/pkg/logger"
|
||||
)
|
||||
|
||||
var API adapter.IControlBridge
|
||||
var App fyne.App
|
||||
var MainWindow fyne.Window
|
||||
var playerWindow fyne.Window
|
||||
var playerWindowHandle uintptr
|
||||
|
||||
func l() adapter.ILogger {
|
||||
return API.Logger().WithModule("GUI")
|
||||
}
|
||||
var logger _logger.ILogger = nil
|
||||
|
||||
func black_magic() {
|
||||
widget.RichTextStyleStrong.TextStyle.Bold = false
|
||||
}
|
||||
|
||||
func Initialize() {
|
||||
logger = global.Logger.WithPrefix("GUI")
|
||||
black_magic()
|
||||
l().Info("Initializing GUI")
|
||||
logger.Info("Initializing GUI")
|
||||
//os.Setenv("FYNE_FONT", config.GetAssetPath("msyh.ttc"))
|
||||
App = app.New()
|
||||
App.Settings().SetTheme(&myTheme{})
|
||||
MainWindow = App.NewWindow(fmt.Sprintf("%s Ver.%s", config.ProgramName, model.Version(config.Version)))
|
||||
MainWindow = App.NewWindow(fmt.Sprintf("%s Ver %s", config2.ProgramName, model.Version(config2.Version)))
|
||||
|
||||
tabs := container.NewAppTabs(
|
||||
container.NewTabItem(i18n.T("gui.tab.player"),
|
||||
@@ -48,15 +48,15 @@ func Initialize() {
|
||||
container.NewTabItem(i18n.T("gui.tab.room"),
|
||||
container.NewBorder(nil, nil, createRoomSelector(), nil, createRoomController()),
|
||||
),
|
||||
container.NewTabItem(i18n.T("gui.tab.playlist"),
|
||||
container.NewBorder(nil, nil, createPlaylists(), nil, createPlaylistMedias()),
|
||||
),
|
||||
container.NewTabItem(i18n.T("gui.tab.history"),
|
||||
container.NewBorder(nil, nil, nil, nil, createHistoryList()),
|
||||
),
|
||||
container.NewTabItem(i18n.T("gui.tab.config"),
|
||||
createConfigLayout(),
|
||||
),
|
||||
//container.NewTabItem(i18n.T("gui.tab.playlist"),
|
||||
// container.NewBorder(nil, nil, createPlaylists(), nil, createPlaylistMedias()),
|
||||
//),
|
||||
//container.NewTabItem(i18n.T("gui.tab.history"),
|
||||
// container.NewBorder(nil, nil, nil, nil, createHistoryList()),
|
||||
//),
|
||||
//container.NewTabItem(i18n.T("gui.tab.config"),
|
||||
// createConfigLayout(),
|
||||
//),
|
||||
)
|
||||
|
||||
tabs.SetTabLocation(container.TabLocationTop)
|
||||
@@ -65,56 +65,41 @@ func Initialize() {
|
||||
//MainWindow.Resize(fyne.NewSize(1280, 720))
|
||||
MainWindow.Resize(fyne.NewSize(960, 480))
|
||||
|
||||
playerWindow = App.NewWindow("CorePlayerPreview")
|
||||
playerWindow.Resize(fyne.NewSize(480, 240))
|
||||
playerWindow.SetCloseIntercept(func() {
|
||||
playerWindow.Hide()
|
||||
})
|
||||
MainWindow.SetOnClosed(func() {
|
||||
playerWindow.Close()
|
||||
})
|
||||
playerWindow.Hide()
|
||||
setupPlayerWindow()
|
||||
|
||||
// register error
|
||||
global.EventManager.RegisterA(
|
||||
events.ErrorUpdate, "gui.show_error", func(e *event.Event) {
|
||||
err := e.Data.(events.ErrorUpdateEvent).Error
|
||||
logger.Warnf("gui received error event: %v, %v", err, err == nil)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
dialog.ShowError(err, MainWindow)
|
||||
})
|
||||
|
||||
//MainWindow.SetFixedSize(true)
|
||||
if config.General.AutoCheckUpdate {
|
||||
go checkUpdate()
|
||||
}
|
||||
//if config2.General.AutoCheckUpdate {
|
||||
// go checkUpdate()
|
||||
//}
|
||||
}
|
||||
|
||||
func showPlayerWindow() {
|
||||
playerWindow.Show()
|
||||
if playerWindowHandle == 0 {
|
||||
playerWindowHandle = util.GetWindowHandle("CorePlayerPreview")
|
||||
l().Infof("video output window handle: %d", playerWindowHandle)
|
||||
if playerWindowHandle != 0 {
|
||||
_ = API.PlayControl().GetPlayer().SetWindowHandle(playerWindowHandle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addShortCut() {
|
||||
key := &desktop.CustomShortcut{KeyName: fyne.KeyRight, Modifier: fyne.KeyModifierControl | fyne.KeyModifierShift}
|
||||
MainWindow.Canvas().AddShortcut(key, func(shortcut fyne.Shortcut) {
|
||||
l().Info("Shortcut pressed: Ctrl+Shift+Right")
|
||||
API.PlayControl().PlayNext()
|
||||
})
|
||||
}
|
||||
|
||||
func checkUpdate() {
|
||||
l().Info("checking updates...")
|
||||
err := API.App().CheckUpdate()
|
||||
if err != nil {
|
||||
showDialogIfError(err)
|
||||
l().Warnf("check update failed", err)
|
||||
return
|
||||
}
|
||||
l().Infof("latest version: v%s", API.App().LatestVersion().Version)
|
||||
if API.App().LatestVersion().Version > API.App().Version().Version {
|
||||
l().Info("new update available")
|
||||
dialog.ShowCustom(
|
||||
i18n.T("gui.update.new_version"),
|
||||
"OK",
|
||||
widget.NewRichTextFromMarkdown(API.App().LatestVersion().Info),
|
||||
MainWindow)
|
||||
}
|
||||
}
|
||||
//
|
||||
//func checkUpdate() {
|
||||
// l().Info("checking updates...")
|
||||
// err := API.App().CheckUpdate()
|
||||
// if err != nil {
|
||||
// showDialogIfError(err)
|
||||
// l().Warnf("check update failed", err)
|
||||
// return
|
||||
// }
|
||||
// l().Infof("latest version: v%s", API.App().LatestVersion().Version)
|
||||
// if API.App().LatestVersion().Version > API.App().Version().Version {
|
||||
// l().Info("new update available")
|
||||
// dialog.ShowCustom(
|
||||
// i18n.T("gui.update.new_version"),
|
||||
// "OK",
|
||||
// widget.NewRichTextFromMarkdown(API.App().LatestVersion().Info),
|
||||
// MainWindow)
|
||||
// }
|
||||
//}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package gutil
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/model"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/storage"
|
||||
"github.com/AynaLivePlayer/miaosic"
|
||||
"github.com/nfnt/resize"
|
||||
"image"
|
||||
"image/png"
|
||||
@@ -27,7 +27,7 @@ func ResizeImage(resource fyne.Resource, width int, height int) fyne.Resource {
|
||||
return fyne.NewStaticResource(resource.Name(), buf.Bytes())
|
||||
}
|
||||
|
||||
func NewImageFromPlayerPicture(picture model.Picture) (*canvas.Image, error) {
|
||||
func NewImageFromPlayerPicture(picture miaosic.Picture) (*canvas.Image, error) {
|
||||
var img *canvas.Image
|
||||
if picture.Data != nil {
|
||||
img = canvas.NewImageFromReader(bytes.NewReader(picture.Data), "cover")
|
||||
|
||||
@@ -3,7 +3,6 @@ package gui
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
@@ -35,12 +34,6 @@ func newContextMenuButton(label string, menu *fyne.Menu) *ContextMenuButton {
|
||||
return b
|
||||
}
|
||||
|
||||
func showDialogIfError(err error) {
|
||||
if err != nil {
|
||||
dialog.ShowError(err, MainWindow)
|
||||
}
|
||||
}
|
||||
|
||||
func newCheckInit(name string, changed func(bool), checked bool) *widget.Check {
|
||||
check := widget.NewCheck(name, changed)
|
||||
check.SetChecked(checked)
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/common/i18n"
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/internal"
|
||||
"fmt"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var History = &struct {
|
||||
Playlist *model.Playlist
|
||||
List *widget.List
|
||||
mux sync.RWMutex
|
||||
}{}
|
||||
|
||||
func createHistoryList() fyne.CanvasObject {
|
||||
History.Playlist = API.Playlists().GetHistory().Model().Copy()
|
||||
History.List = widget.NewList(
|
||||
func() int {
|
||||
return History.Playlist.Size()
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
return container.NewBorder(nil, nil,
|
||||
widget.NewLabel("index"),
|
||||
container.NewHBox(
|
||||
widget.NewButtonWithIcon("", theme.MediaPlayIcon(), nil),
|
||||
widget.NewButtonWithIcon("", theme.ContentAddIcon(), nil),
|
||||
),
|
||||
container.NewGridWithColumns(3,
|
||||
newLabelWithWrapping("title", fyne.TextTruncate),
|
||||
newLabelWithWrapping("artist", fyne.TextTruncate),
|
||||
newLabelWithWrapping("user", fyne.TextTruncate)))
|
||||
},
|
||||
func(id widget.ListItemID, object fyne.CanvasObject) {
|
||||
m := History.Playlist.Medias[History.Playlist.Size()-id-1].Copy()
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*widget.Label).SetText(
|
||||
m.Title)
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(
|
||||
m.Artist)
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[2].(*widget.Label).SetText(
|
||||
m.ToUser().Name)
|
||||
object.(*fyne.Container).Objects[1].(*widget.Label).SetText(fmt.Sprintf("%d", id))
|
||||
btns := object.(*fyne.Container).Objects[2].(*fyne.Container).Objects
|
||||
m.User = internal.HistoryUser
|
||||
btns[0].(*widget.Button).OnTapped = func() {
|
||||
showDialogIfError(API.PlayControl().Play(m))
|
||||
}
|
||||
btns[1].(*widget.Button).OnTapped = func() {
|
||||
API.Playlists().GetCurrent().Push(m)
|
||||
}
|
||||
})
|
||||
registerHistoryHandler()
|
||||
return container.NewBorder(
|
||||
container.NewBorder(nil, nil,
|
||||
widget.NewLabel("#"), widget.NewLabel(i18n.T("gui.history.operation")),
|
||||
container.NewGridWithColumns(3,
|
||||
widget.NewLabel(i18n.T("gui.history.title")),
|
||||
widget.NewLabel(i18n.T("gui.history.artist")),
|
||||
widget.NewLabel(i18n.T("gui.history.user")))),
|
||||
nil, nil, nil,
|
||||
History.List,
|
||||
)
|
||||
}
|
||||
|
||||
func registerHistoryHandler() {
|
||||
API.Playlists().GetHistory().EventManager().RegisterA(events.EventPlaylistUpdate, "gui.history.update", func(event *event.Event) {
|
||||
History.mux.RLock()
|
||||
History.Playlist = event.Data.(events.PlaylistUpdateEvent).Playlist
|
||||
History.List.Refresh()
|
||||
History.mux.RUnlock()
|
||||
})
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package gui
|
||||
|
||||
import "fyne.io/fyne/v2"
|
||||
|
||||
var ConfigList = []ConfigLayout{&bascicConfig{}}
|
||||
|
||||
type ConfigLayout interface {
|
||||
Title() string
|
||||
Description() string
|
||||
CreatePanel() fyne.CanvasObject
|
||||
}
|
||||
|
||||
func AddConfigLayout(cfgs ...ConfigLayout) {
|
||||
ConfigList = append(ConfigList, cfgs...)
|
||||
}
|
||||
179
gui/liverooms.go
179
gui/liverooms.go
@@ -1,14 +1,17 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/common/i18n"
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var RoomTab = &struct {
|
||||
@@ -22,23 +25,40 @@ var RoomTab = &struct {
|
||||
AutoConnect *widget.Check
|
||||
ConnectBtn *widget.Button
|
||||
DisConnectBtn *widget.Button
|
||||
providers []model.LiveRoomProviderInfo
|
||||
rooms []model.LiveRoom
|
||||
lock sync.RWMutex
|
||||
}{}
|
||||
|
||||
func createRoomSelector() fyne.CanvasObject {
|
||||
RoomTab.Rooms = widget.NewList(
|
||||
func() int {
|
||||
return API.LiveRooms().Size()
|
||||
return len(RoomTab.rooms)
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
return widget.NewLabel("AAAAAAAAAAAAAAAA")
|
||||
},
|
||||
func(id widget.ListItemID, object fyne.CanvasObject) {
|
||||
object.(*widget.Label).SetText(
|
||||
API.LiveRooms().Get(id).DisplayName())
|
||||
RoomTab.rooms[id].DisplayName())
|
||||
})
|
||||
RoomTab.AddBtn = widget.NewButton(i18n.T("gui.room.button.add"), func() {
|
||||
clientNameEntry := widget.NewSelect(API.LiveRooms().GetAllClientNames(), nil)
|
||||
providerNames := make([]string, len(RoomTab.providers))
|
||||
for i := 0; i < len(RoomTab.providers); i++ {
|
||||
providerNames[i] = RoomTab.providers[i].Name
|
||||
}
|
||||
descriptionLabel := widget.NewLabel(i18n.T("gui.room.add.prompt"))
|
||||
clientNameEntry := widget.NewSelect(providerNames, func(s string) {
|
||||
for i := 0; i < len(RoomTab.providers); i++ {
|
||||
if RoomTab.providers[i].Name == s {
|
||||
descriptionLabel.SetText(i18n.T(RoomTab.providers[i].Description))
|
||||
break
|
||||
}
|
||||
descriptionLabel.SetText("")
|
||||
}
|
||||
})
|
||||
idEntry := widget.NewEntry()
|
||||
nameEntry := widget.NewEntry()
|
||||
dia := dialog.NewCustomConfirm(
|
||||
i18n.T("gui.room.add.title"),
|
||||
i18n.T("gui.room.add.confirm"),
|
||||
@@ -46,18 +66,25 @@ func createRoomSelector() fyne.CanvasObject {
|
||||
container.NewVBox(
|
||||
container.New(
|
||||
layout.NewFormLayout(),
|
||||
widget.NewLabel(i18n.T("gui.room.add.name")),
|
||||
nameEntry,
|
||||
widget.NewLabel(i18n.T("gui.room.add.client_name")),
|
||||
clientNameEntry,
|
||||
widget.NewLabel(i18n.T("gui.room.add.id_url")),
|
||||
idEntry,
|
||||
),
|
||||
widget.NewLabel(i18n.T("gui.room.add.prompt")),
|
||||
descriptionLabel,
|
||||
),
|
||||
func(b bool) {
|
||||
if b && len(clientNameEntry.Selected) > 0 && len(idEntry.Text) > 0 {
|
||||
_, err := API.LiveRooms().AddRoom(clientNameEntry.Selected, idEntry.Text)
|
||||
showDialogIfError(err)
|
||||
RoomTab.Rooms.Refresh()
|
||||
logger.Infof("Add room %s %s", clientNameEntry.Selected, idEntry.Text)
|
||||
global.EventManager.CallA(
|
||||
events.LiveRoomAddCmd,
|
||||
events.LiveRoomAddCmdEvent{
|
||||
Title: nameEntry.Text,
|
||||
Provider: clientNameEntry.Selected,
|
||||
RoomKey: idEntry.Text,
|
||||
})
|
||||
}
|
||||
},
|
||||
MainWindow,
|
||||
@@ -66,36 +93,32 @@ func createRoomSelector() fyne.CanvasObject {
|
||||
dia.Show()
|
||||
})
|
||||
RoomTab.RemoveBtn = widget.NewButton(i18n.T("gui.room.button.remove"), func() {
|
||||
showDialogIfError(API.LiveRooms().DeleteRoom(PlaylistManager.Index))
|
||||
RoomTab.Rooms.Select(0)
|
||||
RoomTab.Rooms.Refresh()
|
||||
if len(RoomTab.rooms) == 0 {
|
||||
return
|
||||
}
|
||||
global.EventManager.CallA(
|
||||
events.LiveRoomRemoveCmd,
|
||||
events.LiveRoomRemoveCmdEvent{
|
||||
Identifier: RoomTab.rooms[RoomTab.Index].LiveRoom.Identifier(),
|
||||
})
|
||||
})
|
||||
RoomTab.Rooms.OnSelected = func(id widget.ListItemID) {
|
||||
rom := API.LiveRooms().Get(PlaylistManager.Index)
|
||||
if rom != nil {
|
||||
rom.EventManager().Unregister("gui.liveroom.status")
|
||||
if id >= len(RoomTab.rooms) {
|
||||
return
|
||||
}
|
||||
RoomTab.Index = id
|
||||
rom = API.LiveRooms().Get(RoomTab.Index)
|
||||
rom.EventManager().RegisterA(events.LiveRoomStatusChange, "gui.liveroom.status", func(event *event.Event) {
|
||||
d := event.Data.(events.StatusChangeEvent)
|
||||
if d.Connected {
|
||||
RoomTab.Status.SetText(i18n.T("gui.room.status.connected"))
|
||||
} else {
|
||||
RoomTab.Status.SetText(i18n.T("gui.room.status.disconnected"))
|
||||
}
|
||||
RoomTab.Status.Refresh()
|
||||
})
|
||||
RoomTab.RoomTitle.SetText(rom.DisplayName())
|
||||
RoomTab.RoomID.SetText(rom.Identifier())
|
||||
RoomTab.AutoConnect.SetChecked(rom.Model().AutoConnect)
|
||||
if API.LiveRooms().GetRoomStatus(RoomTab.Index) {
|
||||
room := RoomTab.rooms[RoomTab.Index]
|
||||
RoomTab.RoomTitle.SetText(room.DisplayName())
|
||||
RoomTab.RoomID.SetText(room.LiveRoom.Identifier())
|
||||
RoomTab.AutoConnect.SetChecked(room.Config.AutoConnect)
|
||||
if room.Status {
|
||||
RoomTab.Status.SetText(i18n.T("gui.room.status.connected"))
|
||||
} else {
|
||||
RoomTab.Status.SetText(i18n.T("gui.room.status.disconnected"))
|
||||
}
|
||||
RoomTab.Status.Refresh()
|
||||
}
|
||||
registerRoomHandlers()
|
||||
return container.NewHBox(
|
||||
container.NewBorder(
|
||||
nil, container.NewCenter(container.NewHBox(RoomTab.AddBtn, RoomTab.RemoveBtn)),
|
||||
@@ -106,25 +129,107 @@ func createRoomSelector() fyne.CanvasObject {
|
||||
)
|
||||
}
|
||||
|
||||
func registerRoomHandlers() {
|
||||
global.EventManager.RegisterA(
|
||||
events.LiveRoomProviderUpdate,
|
||||
"gui.liveroom.provider_update",
|
||||
func(event *event.Event) {
|
||||
RoomTab.providers = event.Data.(events.LiveRoomProviderUpdateEvent).Providers
|
||||
RoomTab.Rooms.Refresh()
|
||||
})
|
||||
global.EventManager.RegisterA(
|
||||
events.LiveRoomRoomsUpdate,
|
||||
"gui.liveroom.rooms_update",
|
||||
func(event *event.Event) {
|
||||
data := event.Data.(events.LiveRoomRoomsUpdateEvent)
|
||||
RoomTab.lock.Lock()
|
||||
RoomTab.rooms = data.Rooms
|
||||
RoomTab.Rooms.Select(0)
|
||||
RoomTab.Rooms.Refresh()
|
||||
RoomTab.lock.Unlock()
|
||||
})
|
||||
global.EventManager.RegisterA(
|
||||
events.LiveRoomStatusUpdate,
|
||||
"gui.liveroom.room_status_update",
|
||||
func(event *event.Event) {
|
||||
room := event.Data.(events.LiveRoomStatusUpdateEvent).Room
|
||||
index := -1
|
||||
for i := 0; i < len(RoomTab.rooms); i++ {
|
||||
if RoomTab.rooms[i].LiveRoom.Identifier() == room.LiveRoom.Identifier() {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if index == -1 {
|
||||
return
|
||||
}
|
||||
RoomTab.rooms[index] = room
|
||||
RoomTab.Rooms.Refresh()
|
||||
if index == RoomTab.Index {
|
||||
RoomTab.RoomTitle.SetText(room.DisplayName())
|
||||
RoomTab.RoomID.SetText(room.LiveRoom.Identifier())
|
||||
RoomTab.AutoConnect.SetChecked(room.Config.AutoConnect)
|
||||
if room.Status {
|
||||
RoomTab.Status.SetText(i18n.T("gui.room.status.connected"))
|
||||
} else {
|
||||
RoomTab.Status.SetText(i18n.T("gui.room.status.disconnected"))
|
||||
}
|
||||
RoomTab.Status.Refresh()
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func createRoomController() fyne.CanvasObject {
|
||||
RoomTab.ConnectBtn = widget.NewButton(i18n.T("gui.room.btn.connect"), func() {
|
||||
if RoomTab.Index >= len(RoomTab.rooms) {
|
||||
return
|
||||
}
|
||||
RoomTab.ConnectBtn.Disable()
|
||||
go func() {
|
||||
_ = API.LiveRooms().Connect(RoomTab.Index)
|
||||
RoomTab.ConnectBtn.Enable()
|
||||
}()
|
||||
logger.Infof("Connect to room %s", RoomTab.rooms[RoomTab.Index].LiveRoom.Identifier())
|
||||
global.EventManager.CallA(
|
||||
events.LiveRoomOperationCmd,
|
||||
events.LiveRoomOperationCmdEvent{
|
||||
Identifier: RoomTab.rooms[RoomTab.Index].LiveRoom.Identifier(),
|
||||
SetConnect: true,
|
||||
})
|
||||
})
|
||||
RoomTab.DisConnectBtn = widget.NewButton(i18n.T("gui.room.btn.disconnect"), func() {
|
||||
_ = API.LiveRooms().Disconnect(RoomTab.Index)
|
||||
if RoomTab.Index >= len(RoomTab.rooms) {
|
||||
return
|
||||
}
|
||||
RoomTab.DisConnectBtn.Disable()
|
||||
logger.Infof("Disconnect from room %s", RoomTab.rooms[RoomTab.Index].LiveRoom.Identifier())
|
||||
global.EventManager.CallA(
|
||||
events.LiveRoomOperationCmd,
|
||||
events.LiveRoomOperationCmdEvent{
|
||||
Identifier: RoomTab.rooms[RoomTab.Index].LiveRoom.Identifier(),
|
||||
SetConnect: false,
|
||||
})
|
||||
})
|
||||
global.EventManager.RegisterA(
|
||||
events.LiveRoomOperationFinish,
|
||||
"gui.liveroom.operation_finish",
|
||||
func(event *event.Event) {
|
||||
RoomTab.ConnectBtn.Enable()
|
||||
RoomTab.DisConnectBtn.Enable()
|
||||
})
|
||||
RoomTab.Status = widget.NewLabel(i18n.T("gui.room.waiting"))
|
||||
RoomTab.RoomTitle = widget.NewLabel("")
|
||||
RoomTab.RoomID = widget.NewLabel("")
|
||||
RoomTab.AutoConnect = widget.NewCheck(i18n.T("gui.room.check.autoconnect"), func(b bool) {
|
||||
rom := API.LiveRooms().Get(RoomTab.Index)
|
||||
if rom != nil {
|
||||
rom.Model().AutoConnect = b
|
||||
if RoomTab.Index >= len(RoomTab.rooms) {
|
||||
return
|
||||
}
|
||||
logger.Infof("Change room %s autoconnect to %v", RoomTab.rooms[RoomTab.Index].LiveRoom.Identifier(), b)
|
||||
global.EventManager.CallA(
|
||||
events.LiveRoomConfigChangeCmd,
|
||||
events.LiveRoomConfigChangeCmdEvent{
|
||||
Identifier: RoomTab.rooms[RoomTab.Index].LiveRoom.Identifier(),
|
||||
Config: model.LiveRoomConfig{
|
||||
AutoConnect: b,
|
||||
},
|
||||
})
|
||||
return
|
||||
})
|
||||
RoomTab.Rooms.Select(0)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/common/i18n"
|
||||
"AynaLivePlayer/common/util"
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/component"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"AynaLivePlayer/resource"
|
||||
"context"
|
||||
"fyne.io/fyne/v2"
|
||||
@@ -44,13 +44,16 @@ var PlayController = &PlayControllerContainer{}
|
||||
|
||||
func registerPlayControllerHandler() {
|
||||
PlayController.ButtonPrev.OnTapped = func() {
|
||||
API.PlayControl().Seek(0, true)
|
||||
global.EventManager.CallA(events.PlayerSeekCmd, events.PlayerSeekCmdEvent{
|
||||
Position: 0,
|
||||
Absolute: true,
|
||||
})
|
||||
}
|
||||
PlayController.ButtonSwitch.OnTapped = func() {
|
||||
API.PlayControl().Toggle()
|
||||
global.EventManager.CallA(events.PlayerToggleCmd, events.PlayerToggleCmdEvent{})
|
||||
}
|
||||
PlayController.ButtonNext.OnTapped = func() {
|
||||
API.PlayControl().PlayNext()
|
||||
global.EventManager.CallA(events.PlayerPlayNextCmd, events.PlayerPlayNextCmdEvent{})
|
||||
}
|
||||
|
||||
PlayController.ButtonLrc.OnTapped = func() {
|
||||
@@ -64,117 +67,86 @@ func registerPlayControllerHandler() {
|
||||
showPlayerWindow()
|
||||
}
|
||||
|
||||
if API.PlayControl().GetPlayer().ObserveProperty(
|
||||
model.PlayerPropPause, "gui.play_controller.pause", func(ev *event.Event) {
|
||||
data := ev.Data.(events.PlayerPropertyUpdateEvent).Value
|
||||
if data == nil {
|
||||
PlayController.ButtonSwitch.Icon = theme.MediaPlayIcon()
|
||||
return
|
||||
}
|
||||
if data.(bool) {
|
||||
PlayController.ButtonSwitch.Icon = theme.MediaPlayIcon()
|
||||
} else {
|
||||
PlayController.ButtonSwitch.Icon = theme.MediaPauseIcon()
|
||||
}
|
||||
}) != nil {
|
||||
l().Error("fail to register handler for switch button with property pause")
|
||||
}
|
||||
global.EventManager.RegisterA(events.PlayerPropertyPauseUpdate, "gui.player.controller.paused", func(event *event.Event) {
|
||||
if event.Data.(events.PlayerPropertyPauseUpdateEvent).Paused {
|
||||
PlayController.ButtonSwitch.Icon = theme.MediaPlayIcon()
|
||||
} else {
|
||||
PlayController.ButtonSwitch.Icon = theme.MediaPauseIcon()
|
||||
}
|
||||
PlayController.ButtonSwitch.Refresh()
|
||||
})
|
||||
|
||||
if API.PlayControl().GetPlayer().ObserveProperty(
|
||||
model.PlayerPropPercentPos, "gui.play_controller.percent_pos", func(ev *event.Event) {
|
||||
if PlayController.Progress.Dragging {
|
||||
return
|
||||
}
|
||||
data := ev.Data.(events.PlayerPropertyUpdateEvent).Value
|
||||
if data == nil {
|
||||
PlayController.Progress.Value = 0
|
||||
} else {
|
||||
PlayController.Progress.Value = data.(float64) * 10
|
||||
}
|
||||
PlayController.Progress.Refresh()
|
||||
}) != nil {
|
||||
l().Error("fail to register handler for progress bar with property percent-pos")
|
||||
}
|
||||
global.EventManager.RegisterA(events.PlayerPropertyPercentPosUpdate, "gui.player.controller.percent_pos", func(event *event.Event) {
|
||||
if PlayController.Progress.Dragging {
|
||||
return
|
||||
}
|
||||
PlayController.Progress.Value = event.Data.(events.PlayerPropertyPercentPosUpdateEvent).PercentPos * 10
|
||||
PlayController.Progress.Refresh()
|
||||
})
|
||||
|
||||
if API.PlayControl().GetPlayer().ObserveProperty(
|
||||
model.PlayerPropIdleActive, "gui.play_controller.idle_active", func(ev *event.Event) {
|
||||
isIdle := ev.Data.(events.PlayerPropertyUpdateEvent).Value.(bool)
|
||||
l().Debug("receive idle active ", isIdle, " set/reset info")
|
||||
// todo: @3
|
||||
if isIdle {
|
||||
PlayController.Progress.Value = 0
|
||||
PlayController.Progress.Max = 0
|
||||
//PlayController.Title.SetText("Title")
|
||||
//PlayController.Artist.SetText("Artist")
|
||||
//PlayController.Username.SetText("Username")
|
||||
//PlayController.SetDefaultCover()
|
||||
} else {
|
||||
PlayController.Progress.Max = 1000
|
||||
}
|
||||
}) != nil {
|
||||
l().Error("fail to register handler for progress bar with property idle-active")
|
||||
}
|
||||
global.EventManager.RegisterA(events.PlayerPropertyIdleActiveUpdate, "gui.player.controller.idle_active", func(event *event.Event) {
|
||||
isIdle := event.Data.(events.PlayerPropertyIdleActiveUpdateEvent).IsIdle
|
||||
// todo: @3
|
||||
if isIdle {
|
||||
PlayController.Progress.Value = 0
|
||||
PlayController.Progress.Max = 0
|
||||
//PlayController.Title.SetText("Title")
|
||||
//PlayController.Artist.SetText("Artist")
|
||||
//PlayController.Username.SetText("Username")
|
||||
//PlayController.SetDefaultCover()
|
||||
} else {
|
||||
PlayController.Progress.Max = 1000
|
||||
}
|
||||
})
|
||||
|
||||
PlayController.Progress.Max = 0
|
||||
PlayController.Progress.OnDragEnd = func(f float64) {
|
||||
API.PlayControl().Seek(f/10, false)
|
||||
global.EventManager.CallA(events.PlayerSeekCmd, events.PlayerSeekCmdEvent{
|
||||
Position: f / 10,
|
||||
Absolute: false,
|
||||
})
|
||||
}
|
||||
|
||||
if API.PlayControl().GetPlayer().ObserveProperty(
|
||||
model.PlayerPropTimePos, "gui.play_controller.time_pos", func(ev *event.Event) {
|
||||
data := ev.Data.(events.PlayerPropertyUpdateEvent).Value
|
||||
if data == nil {
|
||||
PlayController.CurrentTime.SetText("0:00")
|
||||
return
|
||||
}
|
||||
PlayController.CurrentTime.SetText(util.FormatTime(int(data.(float64))))
|
||||
}) != nil {
|
||||
l().Error("fail to register handler for current time with property time-pos")
|
||||
}
|
||||
global.EventManager.RegisterA(events.PlayerPropertyTimePosUpdate, "gui.player.controller.time_pos", func(event *event.Event) {
|
||||
PlayController.CurrentTime.SetText(util.FormatTime(int(event.Data.(events.PlayerPropertyTimePosUpdateEvent).TimePos)))
|
||||
})
|
||||
|
||||
if API.PlayControl().GetPlayer().ObserveProperty(
|
||||
model.PlayerPropDuration, "gui.play_controller.duration", func(ev *event.Event) {
|
||||
data := ev.Data.(events.PlayerPropertyUpdateEvent).Value
|
||||
if data == nil {
|
||||
PlayController.TotalTime.SetText("0:00")
|
||||
return
|
||||
}
|
||||
PlayController.TotalTime.SetText(util.FormatTime(int(data.(float64))))
|
||||
}) != nil {
|
||||
l().Error("fail to register handler for total time with property duration")
|
||||
}
|
||||
global.EventManager.RegisterA(events.PlayerPropertyDurationUpdate, "gui.player.controller.duration", func(event *event.Event) {
|
||||
PlayController.TotalTime.SetText(util.FormatTime(int(event.Data.(events.PlayerPropertyDurationUpdateEvent).Duration)))
|
||||
})
|
||||
|
||||
if API.PlayControl().GetPlayer().ObserveProperty(
|
||||
model.PlayerPropVolume, "gui.play_controller.volume", func(ev *event.Event) {
|
||||
data := ev.Data.(events.PlayerPropertyUpdateEvent).Value
|
||||
if data == nil {
|
||||
PlayController.Volume.Value = 0
|
||||
} else {
|
||||
PlayController.Volume.Value = data.(float64)
|
||||
}
|
||||
|
||||
PlayController.Volume.Refresh()
|
||||
}) != nil {
|
||||
l().Error("fail to register handler for progress bar with property percent-pos")
|
||||
}
|
||||
global.EventManager.RegisterA(events.PlayerPropertyVolumeUpdate, "gui.player.controller.volume", func(event *event.Event) {
|
||||
PlayController.Volume.Value = event.Data.(events.PlayerPropertyVolumeUpdateEvent).Volume
|
||||
PlayController.Volume.Refresh()
|
||||
})
|
||||
|
||||
PlayController.Volume.OnChanged = func(f float64) {
|
||||
API.PlayControl().SetVolume(f)
|
||||
global.EventManager.CallA(events.PlayerVolumeChangeCmd, events.PlayerVolumeChangeCmdEvent{
|
||||
Volume: f,
|
||||
})
|
||||
}
|
||||
|
||||
API.PlayControl().EventManager().RegisterA(events.EventPlay, "gui.player.updateinfo", func(event *event.Event) {
|
||||
l().Debug("receive EventPlay update player info")
|
||||
media := event.Data.(events.PlayEvent).Media
|
||||
global.EventManager.RegisterA(events.PlayerPlayingUpdate, "gui.player.updateinfo", func(event *event.Event) {
|
||||
if event.Data.(events.PlayerPlayingUpdateEvent).Removed {
|
||||
PlayController.Progress.Value = 0
|
||||
PlayController.Progress.Max = 0
|
||||
PlayController.Title.SetText("Title")
|
||||
PlayController.Artist.SetText("Artist")
|
||||
PlayController.Username.SetText("Username")
|
||||
PlayController.SetDefaultCover()
|
||||
return
|
||||
}
|
||||
media := event.Data.(events.PlayerPlayingUpdateEvent).Media
|
||||
//PlayController.Title.SetText(
|
||||
// util.StringNormalize(media.Title, 16, 16))
|
||||
//PlayController.Artist.SetText(
|
||||
// util.StringNormalize(media.Artist, 16, 16))
|
||||
PlayController.Title.SetText(
|
||||
media.Title)
|
||||
media.Info.Title)
|
||||
PlayController.Artist.SetText(
|
||||
media.Artist)
|
||||
media.Info.Artist)
|
||||
PlayController.Username.SetText(media.ToUser().Name)
|
||||
if !media.Cover.Exists() {
|
||||
if !media.Info.Cover.Exists() {
|
||||
PlayController.SetDefaultCover()
|
||||
} else {
|
||||
if PlayController.coverLoader != nil {
|
||||
@@ -185,7 +157,7 @@ func registerPlayControllerHandler() {
|
||||
go func() {
|
||||
ch := make(chan *canvas.Image)
|
||||
go func() {
|
||||
picture, err := gutil.NewImageFromPlayerPicture(media.Cover)
|
||||
picture, err := gutil.NewImageFromPlayerPicture(media.Info.Cover)
|
||||
if err != nil {
|
||||
ch <- nil
|
||||
return
|
||||
@@ -207,7 +179,6 @@ func registerPlayControllerHandler() {
|
||||
}()
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func createPlayControllerV2() fyne.CanvasObject {
|
||||
@@ -233,7 +204,7 @@ func createPlayControllerV2() fyne.CanvasObject {
|
||||
volumeControl := component.NewFixedHSplitContainer(
|
||||
widget.NewLabel(""),
|
||||
container.NewBorder(nil, nil,
|
||||
widget.NewIcon(theme.VolumeMuteIcon()),
|
||||
widget.NewIcon(theme.VolumeUpIcon()),
|
||||
widget.NewLabel(" "),
|
||||
PlayController.Volume), 0.05)
|
||||
volumeControl.SeparatorThickness = 0
|
||||
@@ -244,15 +215,6 @@ func createPlayControllerV2() fyne.CanvasObject {
|
||||
0.4)
|
||||
controls.SeparatorThickness = 0
|
||||
|
||||
//controls := container.NewPadded(container.NewBorder(nil, nil,
|
||||
// buttonsBox, nil,
|
||||
// container.NewGridWithColumns(
|
||||
// 2,
|
||||
// container.NewMax(),
|
||||
// container.NewBorder(nil, nil, widget.NewIcon(theme.VolumeMuteIcon()), PlayController.ButtonLrc,
|
||||
// PlayController.Volume)),
|
||||
//))
|
||||
|
||||
PlayController.Progress = component.NewSliderPlus(0, 1000)
|
||||
PlayController.CurrentTime = widget.NewLabel("0:00")
|
||||
PlayController.TotalTime = widget.NewLabel("0:00")
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"github.com/AynaLivePlayer/miaosic"
|
||||
)
|
||||
|
||||
func createLyricObj(lyric *model.Lyric) []fyne.CanvasObject {
|
||||
lrcs := make([]fyne.CanvasObject, len(lyric.Lyrics))
|
||||
func createLyricObj(lyric *miaosic.Lyrics) []fyne.CanvasObject {
|
||||
lrcs := make([]fyne.CanvasObject, len(lyric.Content))
|
||||
for i := 0; i < len(lrcs); i++ {
|
||||
lr := widget.NewLabelWithStyle(
|
||||
lyric.Lyrics[i].Lyric,
|
||||
lyric.Content[i].Lyric,
|
||||
fyne.TextAlignCenter, fyne.TextStyle{Italic: true})
|
||||
//lr.Wrapping = fyne.TextWrapWord
|
||||
// todo fix fyne bug
|
||||
@@ -26,10 +28,10 @@ func createLyricObj(lyric *model.Lyric) []fyne.CanvasObject {
|
||||
func createLyricWindow() fyne.Window {
|
||||
|
||||
// create widgets
|
||||
w := App.NewWindow("Lyric")
|
||||
w := App.NewWindow(i18n.T("gui.lyric.title"))
|
||||
currentLrc := newLabelWithWrapping("", fyne.TextWrapBreak)
|
||||
currentLrc.Alignment = fyne.TextAlignCenter
|
||||
fullLrc := container.NewVBox(createLyricObj(API.PlayControl().GetLyric().Get())...)
|
||||
fullLrc := container.NewVBox()
|
||||
lrcWindow := container.NewVScroll(fullLrc)
|
||||
prevIndex := 0
|
||||
w.SetContent(container.NewBorder(nil,
|
||||
@@ -40,50 +42,44 @@ func createLyricWindow() fyne.Window {
|
||||
w.CenterOnScreen()
|
||||
|
||||
// register handlers
|
||||
API.PlayControl().GetLyric().EventManager().RegisterA(
|
||||
events.EventLyricUpdate, "player.lyric.current_lyric", func(event *event.Event) {
|
||||
e := event.Data.(events.LyricUpdateEvent)
|
||||
if prevIndex >= len(fullLrc.Objects) || e.Lyric.Index >= len(fullLrc.Objects) {
|
||||
global.EventManager.RegisterA(
|
||||
events.PlayerLyricPosUpdate, "player.lyric.current_lyric", func(event *event.Event) {
|
||||
e := event.Data.(events.PlayerLyricPosUpdateEvent)
|
||||
logger.Debugf("lyric update", e)
|
||||
if prevIndex >= len(fullLrc.Objects) || e.CurrentIndex >= len(fullLrc.Objects) {
|
||||
// fix race condition
|
||||
return
|
||||
}
|
||||
if e.Lyric == nil {
|
||||
if e.CurrentIndex == -1 {
|
||||
currentLrc.SetText("")
|
||||
return
|
||||
}
|
||||
fullLrc.Objects[prevIndex].(*widget.Label).TextStyle.Bold = false
|
||||
fullLrc.Objects[prevIndex].Refresh()
|
||||
fullLrc.Objects[e.Lyric.Index].(*widget.Label).TextStyle.Bold = true
|
||||
fullLrc.Objects[e.Lyric.Index].Refresh()
|
||||
prevIndex = e.Lyric.Index
|
||||
currentLrc.SetText(e.Lyric.Now.Lyric)
|
||||
fullLrc.Objects[e.CurrentIndex].(*widget.Label).TextStyle.Bold = true
|
||||
fullLrc.Objects[e.CurrentIndex].Refresh()
|
||||
prevIndex = e.CurrentIndex
|
||||
currentLrc.SetText(e.CurrentLine.Lyric)
|
||||
lrcWindow.Scrolled(&fyne.ScrollEvent{
|
||||
Scrolled: fyne.Delta{
|
||||
DX: 0,
|
||||
DY: lrcWindow.Offset.Y - float32(e.Lyric.Index-2)/float32(e.Lyric.Total)*lrcWindow.Content.Size().Height,
|
||||
DY: lrcWindow.Offset.Y - float32(e.CurrentIndex-2)/float32(e.Total)*lrcWindow.Content.Size().Height,
|
||||
},
|
||||
})
|
||||
fullLrc.Refresh()
|
||||
})
|
||||
API.PlayControl().GetLyric().EventManager().RegisterA(
|
||||
events.EventLyricReload, "player.lyric.new_media", func(event *event.Event) {
|
||||
e := event.Data.(events.LyricReloadEvent)
|
||||
lrcs := make([]string, len(e.Lyrics.Lyrics))
|
||||
for i := 0; i < len(lrcs); i++ {
|
||||
lrcs[i] = e.Lyrics.Lyrics[i].Lyric
|
||||
}
|
||||
fullLrc.Objects = createLyricObj(e.Lyrics)
|
||||
//fullLrc.SetText(strings.Join(lrcs, "\n"))
|
||||
//fullLrc.Segments[0] = &widget.TextSegment{
|
||||
// Style: widget.RichTextStyleInline,
|
||||
// Text: strings.Join(lrcs, "\n\n"),
|
||||
//}
|
||||
lrcWindow.Refresh()
|
||||
})
|
||||
|
||||
global.EventManager.RegisterA(events.PlayerLyricReload, "player.lyric.current_lyric", func(event *event.Event) {
|
||||
e := event.Data.(events.PlayerLyricReloadEvent)
|
||||
fullLrc.Objects = createLyricObj(&e.Lyrics)
|
||||
lrcWindow.Refresh()
|
||||
})
|
||||
|
||||
global.EventManager.CallA(events.PlayerLyricRequestCmd, events.PlayerLyricRequestCmdEvent{})
|
||||
|
||||
w.SetOnClosed(func() {
|
||||
API.PlayControl().GetLyric().EventManager().Unregister("player.lyric.current_lyric")
|
||||
API.PlayControl().GetLyric().EventManager().Unregister("player.lyric.new_media")
|
||||
global.EventManager.Unregister("player.lyric.current_lyric")
|
||||
global.EventManager.Unregister("player.lyric.new_media")
|
||||
PlayController.LrcWindowOpen = false
|
||||
})
|
||||
return w
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/common/i18n"
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"fmt"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
@@ -26,10 +27,15 @@ func (b *playlistOperationButton) Tapped(e *fyne.PointEvent) {
|
||||
func newPlaylistOperationButton() *playlistOperationButton {
|
||||
b := &playlistOperationButton{Index: 0}
|
||||
deleteItem := fyne.NewMenuItem(i18n.T("gui.player.playlist.op.delete"), func() {
|
||||
API.Playlists().GetCurrent().Delete(b.Index)
|
||||
global.EventManager.CallA(events.PlaylistDeleteCmd(model.PlaylistIDPlayer), events.PlaylistDeleteCmdEvent{
|
||||
Index: b.Index,
|
||||
})
|
||||
})
|
||||
topItem := fyne.NewMenuItem(i18n.T("gui.player.playlist.op.top"), func() {
|
||||
API.Playlists().GetCurrent().Move(b.Index, 0)
|
||||
global.EventManager.CallA(events.PlaylistMoveCmd(model.PlaylistIDPlayer), events.PlaylistMoveCmdEvent{
|
||||
From: b.Index,
|
||||
To: 0,
|
||||
})
|
||||
})
|
||||
m := fyne.NewMenu("", deleteItem, topItem)
|
||||
b.menu = m
|
||||
@@ -40,17 +46,16 @@ func newPlaylistOperationButton() *playlistOperationButton {
|
||||
}
|
||||
|
||||
var UserPlaylist = &struct {
|
||||
Playlist *model.Playlist
|
||||
List *widget.List
|
||||
mux sync.RWMutex
|
||||
Medias []model.Media
|
||||
List *widget.List
|
||||
mux sync.RWMutex
|
||||
}{}
|
||||
|
||||
func createPlaylist() fyne.CanvasObject {
|
||||
UserPlaylist.Playlist = API.Playlists().GetCurrent().Model().Copy()
|
||||
UserPlaylist.List = widget.NewList(
|
||||
func() int {
|
||||
//todo: @4
|
||||
return UserPlaylist.Playlist.Size()
|
||||
return len(UserPlaylist.Medias)
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
return container.NewBorder(nil, nil, widget.NewLabel("index"), newPlaylistOperationButton(),
|
||||
@@ -60,18 +65,21 @@ func createPlaylist() fyne.CanvasObject {
|
||||
newLabelWithWrapping("user", fyne.TextTruncate)))
|
||||
},
|
||||
func(id widget.ListItemID, object fyne.CanvasObject) {
|
||||
l().Debugf("Update playlist item: %d", id)
|
||||
l().Debugf("Update playlist event during update %d", UserPlaylist.Playlist.Size())
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*widget.Label).SetText(
|
||||
UserPlaylist.Playlist.Medias[id].Title)
|
||||
UserPlaylist.Medias[id].Info.Title)
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(
|
||||
UserPlaylist.Playlist.Medias[id].Artist)
|
||||
UserPlaylist.Medias[id].Info.Artist)
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[2].(*widget.Label).SetText(
|
||||
UserPlaylist.Playlist.Medias[id].ToUser().Name)
|
||||
UserPlaylist.Medias[id].ToUser().Name)
|
||||
object.(*fyne.Container).Objects[1].(*widget.Label).SetText(fmt.Sprintf("%d", id))
|
||||
object.(*fyne.Container).Objects[2].(*playlistOperationButton).Index = id
|
||||
})
|
||||
registerPlaylistHandler()
|
||||
global.EventManager.RegisterA(events.PlaylistDetailUpdate(model.PlaylistIDPlayer), "gui.player.playlist.update", func(event *event.Event) {
|
||||
UserPlaylist.mux.Lock()
|
||||
UserPlaylist.Medias = event.Data.(events.PlaylistDetailUpdateEvent).Medias
|
||||
UserPlaylist.List.Refresh()
|
||||
UserPlaylist.mux.Unlock()
|
||||
})
|
||||
return container.NewBorder(
|
||||
container.NewBorder(nil, nil,
|
||||
widget.NewLabel("#"), widget.NewLabel(i18n.T("gui.player.playlist.ops")),
|
||||
@@ -84,12 +92,3 @@ func createPlaylist() fyne.CanvasObject {
|
||||
UserPlaylist.List,
|
||||
)
|
||||
}
|
||||
|
||||
func registerPlaylistHandler() {
|
||||
API.Playlists().GetCurrent().EventManager().RegisterA(events.EventPlaylistUpdate, "gui.playlist.update", func(event *event.Event) {
|
||||
UserPlaylist.mux.Lock()
|
||||
UserPlaylist.Playlist = event.Data.(events.PlaylistUpdateEvent).Playlist
|
||||
UserPlaylist.List.Refresh()
|
||||
UserPlaylist.mux.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
32
gui/player_videoplayer.go
Normal file
32
gui/player_videoplayer.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/xfyne"
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
func setupPlayerWindow() {
|
||||
playerWindow = App.NewWindow("CorePlayerPreview")
|
||||
playerWindow.Resize(fyne.NewSize(480, 240))
|
||||
playerWindow.SetCloseIntercept(func() {
|
||||
playerWindow.Hide()
|
||||
})
|
||||
MainWindow.SetOnClosed(func() {
|
||||
playerWindow.Close()
|
||||
})
|
||||
playerWindow.Hide()
|
||||
}
|
||||
|
||||
func showPlayerWindow() {
|
||||
playerWindow.Show()
|
||||
if playerWindowHandle == 0 {
|
||||
playerWindowHandle = xfyne.GetWindowHandle(playerWindow)
|
||||
logger.Infof("video output window handle: %d", playerWindowHandle)
|
||||
if playerWindowHandle != 0 {
|
||||
global.EventManager.CallA(events.PlayerVideoPlayerSetWindowHandleCmd,
|
||||
events.PlayerVideoPlayerSetWindowHandleCmdEvent{Handle: playerWindowHandle})
|
||||
}
|
||||
}
|
||||
}
|
||||
150
gui/playlists.go
150
gui/playlists.go
@@ -1,150 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/i18n"
|
||||
"AynaLivePlayer/gui/component"
|
||||
"AynaLivePlayer/internal"
|
||||
"fmt"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type PlaylistsTab struct {
|
||||
Playlists *widget.List
|
||||
PlaylistMedia *widget.List
|
||||
Index int
|
||||
AddBtn *widget.Button
|
||||
RemoveBtn *widget.Button
|
||||
SetAsSystemBtn *component.AsyncButton
|
||||
RefreshBtn *component.AsyncButton
|
||||
CurrentSystemPlaylist *widget.Label
|
||||
}
|
||||
|
||||
func (p *PlaylistsTab) UpdateCurrentSystemPlaylist() {
|
||||
p.CurrentSystemPlaylist.SetText(i18n.T("gui.playlist.current") + API.Playlists().GetDefault().DisplayName())
|
||||
}
|
||||
|
||||
var PlaylistManager = &PlaylistsTab{}
|
||||
|
||||
func createPlaylists() fyne.CanvasObject {
|
||||
PlaylistManager.Playlists = widget.NewList(
|
||||
func() int {
|
||||
return API.Playlists().Size()
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
return widget.NewLabel("AAAAAAAAAAAAAAAA")
|
||||
},
|
||||
func(id widget.ListItemID, object fyne.CanvasObject) {
|
||||
object.(*widget.Label).SetText(
|
||||
API.Playlists().Get(id).DisplayName())
|
||||
})
|
||||
PlaylistManager.AddBtn = widget.NewButton(i18n.T("gui.playlist.button.add"), func() {
|
||||
providerEntry := widget.NewSelect(API.Provider().GetPriority(), nil)
|
||||
idEntry := widget.NewEntry()
|
||||
dia := dialog.NewCustomConfirm(
|
||||
i18n.T("gui.playlist.add.title"),
|
||||
i18n.T("gui.playlist.add.confirm"),
|
||||
i18n.T("gui.playlist.add.cancel"),
|
||||
container.NewVBox(
|
||||
container.New(
|
||||
layout.NewFormLayout(),
|
||||
widget.NewLabel(i18n.T("gui.playlist.add.source")),
|
||||
providerEntry,
|
||||
widget.NewLabel(i18n.T("gui.playlist.add.id_url")),
|
||||
idEntry,
|
||||
),
|
||||
widget.NewLabel(i18n.T("gui.playlist.add.prompt")),
|
||||
),
|
||||
func(b bool) {
|
||||
if b && len(providerEntry.Selected) > 0 && len(idEntry.Text) > 0 {
|
||||
API.Playlists().Add(providerEntry.Selected, idEntry.Text)
|
||||
PlaylistManager.Playlists.Refresh()
|
||||
PlaylistManager.PlaylistMedia.Refresh()
|
||||
}
|
||||
},
|
||||
MainWindow,
|
||||
)
|
||||
dia.Resize(fyne.NewSize(512, 256))
|
||||
dia.Show()
|
||||
})
|
||||
PlaylistManager.RemoveBtn = widget.NewButton(i18n.T("gui.playlist.button.remove"), func() {
|
||||
API.Playlists().Remove(PlaylistManager.Index)
|
||||
//PlaylistManager.Index = 0
|
||||
PlaylistManager.Playlists.Select(0)
|
||||
PlaylistManager.Playlists.Refresh()
|
||||
PlaylistManager.PlaylistMedia.Refresh()
|
||||
})
|
||||
PlaylistManager.Playlists.OnSelected = func(id widget.ListItemID) {
|
||||
PlaylistManager.Index = id
|
||||
PlaylistManager.PlaylistMedia.Refresh()
|
||||
}
|
||||
return container.NewHBox(
|
||||
container.NewBorder(
|
||||
nil, container.NewCenter(container.NewHBox(PlaylistManager.AddBtn, PlaylistManager.RemoveBtn)),
|
||||
nil, nil,
|
||||
PlaylistManager.Playlists,
|
||||
),
|
||||
widget.NewSeparator(),
|
||||
)
|
||||
}
|
||||
|
||||
func createPlaylistMedias() fyne.CanvasObject {
|
||||
PlaylistManager.RefreshBtn = component.NewAsyncButtonWithIcon(
|
||||
i18n.T("gui.playlist.button.refresh"), theme.ViewRefreshIcon(),
|
||||
func() {
|
||||
showDialogIfError(API.Playlists().PreparePlaylistByIndex(PlaylistManager.Index))
|
||||
PlaylistManager.PlaylistMedia.Refresh()
|
||||
})
|
||||
PlaylistManager.SetAsSystemBtn = component.NewAsyncButton(
|
||||
i18n.T("gui.playlist.button.set_as_system"),
|
||||
func() {
|
||||
showDialogIfError(API.Playlists().SetDefault(PlaylistManager.Index))
|
||||
PlaylistManager.PlaylistMedia.Refresh()
|
||||
PlaylistManager.UpdateCurrentSystemPlaylist()
|
||||
})
|
||||
|
||||
PlaylistManager.CurrentSystemPlaylist = widget.NewLabel("Current: ")
|
||||
PlaylistManager.UpdateCurrentSystemPlaylist()
|
||||
PlaylistManager.PlaylistMedia = widget.NewList(
|
||||
func() int {
|
||||
if API.Playlists().Size() == 0 {
|
||||
return 0
|
||||
}
|
||||
return API.Playlists().Get(PlaylistManager.Index).Size()
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
return container.NewBorder(nil, nil,
|
||||
widget.NewLabel("index"),
|
||||
container.NewHBox(
|
||||
widget.NewButtonWithIcon("", theme.MediaPlayIcon(), nil),
|
||||
widget.NewButtonWithIcon("", theme.ContentAddIcon(), nil),
|
||||
),
|
||||
container.NewGridWithColumns(2,
|
||||
newLabelWithWrapping("title", fyne.TextTruncate),
|
||||
newLabelWithWrapping("artist", fyne.TextTruncate)))
|
||||
},
|
||||
func(id widget.ListItemID, object fyne.CanvasObject) {
|
||||
m := API.Playlists().Get(PlaylistManager.Index).Get(id).Copy()
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*widget.Label).SetText(
|
||||
m.Title)
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(
|
||||
m.Artist)
|
||||
object.(*fyne.Container).Objects[1].(*widget.Label).SetText(fmt.Sprintf("%d", id))
|
||||
btns := object.(*fyne.Container).Objects[2].(*fyne.Container).Objects
|
||||
m.User = internal.SystemUser
|
||||
btns[0].(*widget.Button).OnTapped = func() {
|
||||
showDialogIfError(API.PlayControl().Play(m))
|
||||
}
|
||||
btns[1].(*widget.Button).OnTapped = func() {
|
||||
API.Playlists().GetCurrent().Push(m)
|
||||
}
|
||||
})
|
||||
return container.NewBorder(
|
||||
container.NewHBox(PlaylistManager.RefreshBtn, PlaylistManager.SetAsSystemBtn, PlaylistManager.CurrentSystemPlaylist), nil,
|
||||
nil, nil,
|
||||
PlaylistManager.PlaylistMedia)
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/i18n"
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/component"
|
||||
"AynaLivePlayer/internal"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
@@ -28,22 +29,31 @@ func createSearchBar() fyne.CanvasObject {
|
||||
SearchBar.Button = component.NewAsyncButton(i18n.T("gui.search.search"), func() {
|
||||
keyword := SearchBar.Input.Text
|
||||
pr := SearchBar.UseSource.Selected
|
||||
l().Debugf("Search keyword: %s, provider: %s", keyword, pr)
|
||||
items, err := API.Provider().SearchWithProvider(keyword, pr)
|
||||
if err != nil {
|
||||
dialog.ShowError(err, MainWindow)
|
||||
}
|
||||
model.ApplyUser(items, internal.SystemUser)
|
||||
SearchResult.Items = items
|
||||
logger.Debugf("Search keyword: %s, provider: %s", keyword, pr)
|
||||
SearchResult.mux.Lock()
|
||||
SearchResult.Items = make([]model.Media, 0)
|
||||
SearchResult.List.Refresh()
|
||||
SearchResult.mux.Unlock()
|
||||
global.EventManager.CallA(events.SearchCmd, events.SearchCmdEvent{
|
||||
Keyword: keyword,
|
||||
Provider: pr,
|
||||
})
|
||||
})
|
||||
|
||||
global.EventManager.RegisterA(events.SearchProviderUpdate,
|
||||
"gui.search.provider.update", func(event *event.Event) {
|
||||
providers := event.Data.(events.SearchProviderUpdateEvent)
|
||||
s := make([]string, len(providers.Providers))
|
||||
copy(s, providers.Providers)
|
||||
SearchBar.UseSource.Options = s
|
||||
if len(s) > 0 {
|
||||
SearchBar.UseSource.SetSelected(s[0])
|
||||
}
|
||||
})
|
||||
|
||||
SearchBar.UseSource = widget.NewSelect([]string{}, func(s string) {
|
||||
})
|
||||
s := make([]string, len(API.Provider().GetPriority()))
|
||||
copy(s, API.Provider().GetPriority())
|
||||
|
||||
SearchBar.UseSource = widget.NewSelect(s, func(s string) {})
|
||||
if len(s) > 0 {
|
||||
SearchBar.UseSource.SetSelected(s[0])
|
||||
}
|
||||
searchInput := container.NewBorder(
|
||||
nil, nil, widget.NewLabel(i18n.T("gui.search.search")), SearchBar.Button,
|
||||
container.NewBorder(nil, nil, SearchBar.UseSource, nil, SearchBar.Input))
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/i18n"
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"fmt"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var SearchResult = &struct {
|
||||
List *widget.List
|
||||
Items []*model.Media
|
||||
Items []model.Media
|
||||
mux sync.Mutex
|
||||
}{
|
||||
Items: []*model.Media{},
|
||||
Items: []model.Media{},
|
||||
mux: sync.Mutex{},
|
||||
}
|
||||
|
||||
func createSearchList() fyne.CanvasObject {
|
||||
@@ -36,20 +42,32 @@ func createSearchList() fyne.CanvasObject {
|
||||
},
|
||||
func(id widget.ListItemID, object fyne.CanvasObject) {
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*widget.Label).SetText(
|
||||
SearchResult.Items[id].Title)
|
||||
SearchResult.Items[id].Info.Title)
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(
|
||||
SearchResult.Items[id].Artist)
|
||||
SearchResult.Items[id].Info.Artist)
|
||||
object.(*fyne.Container).Objects[0].(*fyne.Container).Objects[2].(*widget.Label).SetText(
|
||||
SearchResult.Items[id].Meta.(model.Meta).Name)
|
||||
SearchResult.Items[id].Info.Meta.Provider)
|
||||
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() {
|
||||
showDialogIfError(API.PlayControl().Play(SearchResult.Items[id]))
|
||||
global.EventManager.CallA(events.PlayerPlayCmd, events.PlayerPlayCmdEvent{
|
||||
Media: SearchResult.Items[id],
|
||||
})
|
||||
}
|
||||
btns[1].(*widget.Button).OnTapped = func() {
|
||||
API.Playlists().GetCurrent().Push(SearchResult.Items[id])
|
||||
global.EventManager.CallA(events.PlaylistInsertCmd(model.PlaylistIDPlayer), events.PlaylistInsertCmdEvent{
|
||||
Media: SearchResult.Items[id],
|
||||
Position: -1,
|
||||
})
|
||||
}
|
||||
})
|
||||
global.EventManager.RegisterA(events.SearchResultUpdate, "gui.search.update_result", func(event *event.Event) {
|
||||
items := event.Data.(events.SearchResultUpdateEvent).Medias
|
||||
SearchResult.Items = items
|
||||
SearchResult.mux.Lock()
|
||||
SearchResult.List.Refresh()
|
||||
SearchResult.mux.Unlock()
|
||||
})
|
||||
return container.NewBorder(
|
||||
container.NewBorder(nil, nil,
|
||||
widget.NewLabel("#"), widget.NewLabel(i18n.T("gui.search.operation")),
|
||||
|
||||
10
gui/theme.go
10
gui/theme.go
@@ -3,8 +3,9 @@ package gui
|
||||
import (
|
||||
"AynaLivePlayer/resource"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"image/color"
|
||||
|
||||
xtheme "fyne.io/x/fyne/theme"
|
||||
)
|
||||
|
||||
type myTheme struct{}
|
||||
@@ -18,7 +19,6 @@ func (*myTheme) Font(s fyne.TextStyle) fyne.Resource {
|
||||
}
|
||||
if s.Bold {
|
||||
if s.Italic {
|
||||
//return theme.DefaultTheme().Font(s)
|
||||
return resource.FontMSYaHeiBold
|
||||
}
|
||||
return resource.FontMSYaHei
|
||||
@@ -30,13 +30,13 @@ func (*myTheme) Font(s fyne.TextStyle) fyne.Resource {
|
||||
}
|
||||
|
||||
func (*myTheme) Color(n fyne.ThemeColorName, v fyne.ThemeVariant) color.Color {
|
||||
return theme.DefaultTheme().Color(n, v)
|
||||
return xtheme.AdwaitaTheme().Color(n, v)
|
||||
}
|
||||
|
||||
func (*myTheme) Icon(n fyne.ThemeIconName) fyne.Resource {
|
||||
return theme.DefaultTheme().Icon(n)
|
||||
return xtheme.AdwaitaTheme().Icon(n)
|
||||
}
|
||||
|
||||
func (*myTheme) Size(n fyne.ThemeSizeName) float32 {
|
||||
return theme.DefaultTheme().Size(n)
|
||||
return xtheme.AdwaitaTheme().Size(n)
|
||||
}
|
||||
|
||||
23
gui/xfyne/window.go
Normal file
23
gui/xfyne/window.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package xfyne
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"github.com/go-gl/glfw/v3.3/glfw"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// getGlfwWindow returns the glfw.Window pointer from a fyne.Window.
|
||||
// very unsafe and ugly hacks. but it works.
|
||||
func getGlfwWindow(window fyne.Window) *glfw.Window {
|
||||
rv := reflect.ValueOf(window)
|
||||
if rv.Type().String() != "*glfw.window" {
|
||||
return nil
|
||||
}
|
||||
rv = rv.Elem()
|
||||
var glfwWindowPtr uintptr = rv.FieldByName("viewport").Pointer()
|
||||
for glfwWindowPtr == 0 {
|
||||
glfwWindowPtr = rv.FieldByName("viewport").Pointer()
|
||||
}
|
||||
return (*glfw.Window)(unsafe.Pointer(glfwWindowPtr))
|
||||
}
|
||||
12
gui/xfyne/window_darwin.go
Normal file
12
gui/xfyne/window_darwin.go
Normal file
@@ -0,0 +1,12 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package xfyne
|
||||
|
||||
func GetWindowHandle(window fyne.Window) uintptr {
|
||||
glfwWindow := getGlfwWindow(window)
|
||||
if glfwWindow == nil {
|
||||
return 0
|
||||
}
|
||||
return uintptr(glfwWindow.GetCocoaWindow())
|
||||
}
|
||||
16
gui/xfyne/window_linux.go
Normal file
16
gui/xfyne/window_linux.go
Normal file
@@ -0,0 +1,16 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package xfyne
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
func GetWindowHandle(window fyne.Window) uintptr {
|
||||
glfwWindow := getGlfwWindow(window)
|
||||
if glfwWindow == nil {
|
||||
return 0
|
||||
}
|
||||
return uintptr(glfwWindow.GetX11Window())
|
||||
}
|
||||
10
gui/xfyne/window_other.go
Normal file
10
gui/xfyne/window_other.go
Normal file
@@ -0,0 +1,10 @@
|
||||
//go:build !darwin && !windows && !linux
|
||||
// +build !darwin,!windows,!linux
|
||||
|
||||
package xfyne
|
||||
|
||||
import "fyne.io/fyne/v2"
|
||||
|
||||
func GetWindowHandle(window fyne.Window) uintptr {
|
||||
return 0
|
||||
}
|
||||
14
gui/xfyne/window_windows.go
Normal file
14
gui/xfyne/window_windows.go
Normal file
@@ -0,0 +1,14 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package xfyne
|
||||
|
||||
import "fyne.io/fyne/v2"
|
||||
|
||||
func GetWindowHandle(window fyne.Window) uintptr {
|
||||
glfwWindow := getGlfwWindow(window)
|
||||
if glfwWindow == nil {
|
||||
return 0
|
||||
}
|
||||
return uintptr(glfwWindow.GetWin32Window())
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/adapter"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
app adapter.IApplication `ini:"-"`
|
||||
liveroom adapter.ILiveRoomController `ini:"-"`
|
||||
player adapter.IPlayController `ini:"-"`
|
||||
lyric adapter.ILyricLoader `ini:"-"`
|
||||
playlist adapter.IPlaylistController `ini:"-"`
|
||||
provider adapter.IProviderController `ini:"-"`
|
||||
plugin adapter.IPluginController `ini:"-"`
|
||||
log adapter.ILogger `ini:"-"`
|
||||
}
|
||||
|
||||
func (c *Controller) Logger() adapter.ILogger {
|
||||
return c.log
|
||||
}
|
||||
|
||||
func NewController(
|
||||
liveroom adapter.ILiveRoomController, player adapter.IPlayController,
|
||||
playlist adapter.IPlaylistController,
|
||||
provider adapter.IProviderController, plugin adapter.IPluginController,
|
||||
log adapter.ILogger) adapter.IControlBridge {
|
||||
cc := &Controller{
|
||||
app: &AppBilibiliChannel{},
|
||||
liveroom: liveroom,
|
||||
player: player,
|
||||
playlist: playlist,
|
||||
provider: provider,
|
||||
plugin: plugin,
|
||||
log: log,
|
||||
}
|
||||
return cc
|
||||
}
|
||||
|
||||
func (c *Controller) App() adapter.IApplication {
|
||||
return c.app
|
||||
}
|
||||
|
||||
func (c *Controller) LiveRooms() adapter.ILiveRoomController {
|
||||
return c.liveroom
|
||||
}
|
||||
|
||||
func (c *Controller) PlayControl() adapter.IPlayController {
|
||||
return c.player
|
||||
}
|
||||
|
||||
func (c *Controller) Playlists() adapter.IPlaylistController {
|
||||
return c.playlist
|
||||
}
|
||||
|
||||
func (c *Controller) Provider() adapter.IProviderController {
|
||||
return c.provider
|
||||
}
|
||||
|
||||
func (c *Controller) Plugin() adapter.IPluginController {
|
||||
return c.plugin
|
||||
}
|
||||
|
||||
func (c *Controller) LoadPlugins(plugins ...adapter.Plugin) {
|
||||
c.plugin.LoadPlugins(plugins...)
|
||||
}
|
||||
|
||||
func (c *Controller) CloseAndSave() {
|
||||
c.plugin.ClosePlugins()
|
||||
}
|
||||
65
internal/controller/controller.go
Normal file
65
internal/controller/controller.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
)
|
||||
|
||||
func Initialize() {
|
||||
handleSearch()
|
||||
createLyricLoader()
|
||||
handlePlayNext()
|
||||
}
|
||||
|
||||
func Stop() {
|
||||
|
||||
}
|
||||
|
||||
func handlePlayNext() {
|
||||
log := global.Logger.WithPrefix("Controller")
|
||||
isIdle := false
|
||||
global.EventManager.RegisterA(
|
||||
events.PlayerPropertyIdleActiveUpdate,
|
||||
"internal.controller.playcontrol.idleplaynext",
|
||||
func(event *event.Event) {
|
||||
data := event.Data.(events.PlayerPropertyIdleActiveUpdateEvent)
|
||||
isIdle = data.IsIdle
|
||||
if data.IsIdle {
|
||||
log.Info("mpv went idle, try play next")
|
||||
global.EventManager.CallA(events.PlayerPlayNextCmd,
|
||||
events.PlayerPlayNextCmdEvent{})
|
||||
}
|
||||
})
|
||||
|
||||
global.EventManager.RegisterA(
|
||||
events.PlaylistInsertUpdate(model.PlaylistIDPlayer),
|
||||
"internal.controller.playcontrol.playnext_when_insert",
|
||||
func(event *event.Event) {
|
||||
if isIdle {
|
||||
global.EventManager.CallA(events.PlayerPlayNextCmd,
|
||||
events.PlayerPlayNextCmdEvent{})
|
||||
}
|
||||
})
|
||||
|
||||
global.EventManager.RegisterA(
|
||||
events.PlayerPlayNextCmd,
|
||||
"internal.controller.playcontrol.playnext",
|
||||
func(event *event.Event) {
|
||||
global.EventManager.CallA(events.PlaylistNextCmd(model.PlaylistIDPlayer),
|
||||
events.PlaylistNextCmdEvent{
|
||||
Remove: true,
|
||||
})
|
||||
})
|
||||
|
||||
global.EventManager.RegisterA(events.PlaylistNextUpdate(model.PlaylistIDPlayer),
|
||||
"internal.controller.playcontrol.play_when_next", func(event *event.Event) {
|
||||
data := event.Data.(events.PlaylistNextUpdateEvent)
|
||||
global.EventManager.CallA(
|
||||
events.PlayerPlayCmd,
|
||||
events.PlayerPlayCmdEvent{
|
||||
Media: data.Media,
|
||||
})
|
||||
})
|
||||
}
|
||||
60
internal/controller/lyric.go
Normal file
60
internal/controller/lyric.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"github.com/AynaLivePlayer/miaosic"
|
||||
)
|
||||
|
||||
type lyricLoader struct {
|
||||
Lyric miaosic.Lyrics
|
||||
prev float64
|
||||
prevIndex int
|
||||
}
|
||||
|
||||
var lyricManager = &lyricLoader{}
|
||||
|
||||
func createLyricLoader() {
|
||||
log := global.Logger.WithPrefix("LyricLoader")
|
||||
global.EventManager.RegisterA(events.PlayerPlayingUpdate, "internal.lyric.update", func(event *event.Event) {
|
||||
data := event.Data.(events.PlayerPlayingUpdateEvent)
|
||||
if data.Removed {
|
||||
log.Debugf("current media removed, clear lyric")
|
||||
lyricManager.Lyric = miaosic.ParseLyrics("", "")
|
||||
return
|
||||
}
|
||||
log.Infof("update lyric for %s", data.Media.Info.Title)
|
||||
lyric, err := miaosic.GetMediaLyric(data.Media.Info.Meta)
|
||||
if err == nil && len(lyric) > 0 {
|
||||
lyricManager.Lyric = lyric[0]
|
||||
} else {
|
||||
log.Errorf("failed to get lyric for %s (%s): %s", data.Media.Info.Title, data.Media.Info.Meta.ID(), err)
|
||||
}
|
||||
global.EventManager.CallA(events.PlayerLyricReload, events.PlayerLyricReloadEvent{
|
||||
Lyrics: lyricManager.Lyric,
|
||||
})
|
||||
})
|
||||
global.EventManager.RegisterA(events.PlayerPropertyTimePosUpdate, "internal.lyric.update_current", func(event *event.Event) {
|
||||
time := event.Data.(events.PlayerPropertyTimePosUpdateEvent).TimePos
|
||||
idx := lyricManager.Lyric.FindIndex(time)
|
||||
if idx == lyricManager.prevIndex {
|
||||
return
|
||||
}
|
||||
lyricManager.prevIndex = idx
|
||||
global.EventManager.CallA(
|
||||
events.PlayerLyricPosUpdate,
|
||||
events.PlayerLyricPosUpdateEvent{
|
||||
CurrentIndex: idx,
|
||||
Time: time,
|
||||
CurrentLine: lyricManager.Lyric.Find(time),
|
||||
Total: len(lyricManager.Lyric.Content),
|
||||
})
|
||||
return
|
||||
})
|
||||
global.EventManager.RegisterA(events.PlayerLyricRequestCmd, "internal.lyric.request", func(event *event.Event) {
|
||||
global.EventManager.CallA(events.PlayerLyricReload, events.PlayerLyricReloadEvent{
|
||||
Lyrics: lyricManager.Lyric,
|
||||
})
|
||||
})
|
||||
}
|
||||
38
internal/controller/search.go
Normal file
38
internal/controller/search.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"github.com/AynaLivePlayer/miaosic"
|
||||
)
|
||||
|
||||
func handleSearch() {
|
||||
log := global.Logger.WithPrefix("Search")
|
||||
global.EventManager.RegisterA(
|
||||
events.SearchCmd, "internal.controller.search.handleSearchCmd", func(event *event.Event) {
|
||||
data := event.Data.(events.SearchCmdEvent)
|
||||
log.Infof("Search %s using %s", data.Keyword, data.Provider)
|
||||
searchResult, err := miaosic.SearchByProvider(data.Provider, data.Keyword, 1, 10)
|
||||
if err != nil {
|
||||
log.Warnf("Search %s using %s failed: %s", data.Keyword, data.Provider, err)
|
||||
return
|
||||
}
|
||||
medias := make([]model.Media, len(searchResult))
|
||||
for i, v := range searchResult {
|
||||
medias[i] = model.Media{
|
||||
Info: v,
|
||||
User: model.SystemUser,
|
||||
}
|
||||
}
|
||||
global.EventManager.CallA(
|
||||
events.SearchResultUpdate, events.SearchResultUpdateEvent{
|
||||
Medias: medias,
|
||||
})
|
||||
})
|
||||
global.EventManager.CallA(
|
||||
events.SearchProviderUpdate, events.SearchProviderUpdateEvent{
|
||||
Providers: miaosic.ListAvailableProviders(),
|
||||
})
|
||||
}
|
||||
6
internal/init.go
Normal file
6
internal/init.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
_ "github.com/AynaLivePlayer/miaosic/providers/bilivideo"
|
||||
_ "github.com/AynaLivePlayer/miaosic/providers/netease"
|
||||
)
|
||||
@@ -1 +1,20 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/internal/controller"
|
||||
"AynaLivePlayer/internal/liveroom"
|
||||
"AynaLivePlayer/internal/player"
|
||||
"AynaLivePlayer/internal/playlist"
|
||||
)
|
||||
|
||||
func Initialize() {
|
||||
player.SetupMpvPlayer()
|
||||
playlist.Initialize()
|
||||
controller.Initialize()
|
||||
liveroom.Initialize()
|
||||
}
|
||||
|
||||
func Stop() {
|
||||
liveroom.StopAndSave()
|
||||
player.StopMpvPlayer()
|
||||
}
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/adapters"
|
||||
"AynaLivePlayer/common/config"
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/core/adapter"
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type liveRoomImpl struct {
|
||||
model.LiveRoom
|
||||
client adapter.LiveClient
|
||||
}
|
||||
|
||||
func (r *liveRoomImpl) Status() bool {
|
||||
return r.client.Status()
|
||||
}
|
||||
|
||||
func (r *liveRoomImpl) EventManager() *event.Manager {
|
||||
return r.client.EventManager()
|
||||
}
|
||||
|
||||
func (r *liveRoomImpl) Model() *model.LiveRoom {
|
||||
return &r.LiveRoom
|
||||
}
|
||||
|
||||
func (r *liveRoomImpl) Client() adapter.LiveClient {
|
||||
return r.client
|
||||
}
|
||||
|
||||
func (r *liveRoomImpl) DisplayName() string {
|
||||
// todo need to be fixed
|
||||
r.LiveRoom.Title = r.client.RoomName()
|
||||
if r.LiveRoom.Title != "" {
|
||||
return r.LiveRoom.Title
|
||||
}
|
||||
return r.LiveRoom.Identifier()
|
||||
}
|
||||
|
||||
func (r *liveRoomImpl) init(msgHandler event.HandlerFunc) (err error) {
|
||||
if r.client != nil {
|
||||
return nil
|
||||
}
|
||||
r.client, err = adapters.LiveClient.NewLiveClient(r.ClientName, r.ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r.LiveRoom.Title = r.client.RoomName()
|
||||
r.client.EventManager().RegisterA(
|
||||
events.LiveRoomMessageReceive,
|
||||
"adapter.danmu.command",
|
||||
msgHandler)
|
||||
return nil
|
||||
}
|
||||
|
||||
type LiveRoomController struct {
|
||||
LiveRoomPath string
|
||||
liveRooms []*liveRoomImpl
|
||||
danmuCommands []adapter.LiveRoomExecutor
|
||||
log adapter.ILogger
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) GetAllClientNames() []string {
|
||||
return adapters.LiveClient.GetAllClientNames()
|
||||
}
|
||||
|
||||
func NewLiveRoomController(
|
||||
log adapter.ILogger,
|
||||
) adapter.ILiveRoomController {
|
||||
lr := &LiveRoomController{
|
||||
LiveRoomPath: "liverooms.json",
|
||||
liveRooms: []*liveRoomImpl{
|
||||
{LiveRoom: model.LiveRoom{
|
||||
ClientName: "bilibili",
|
||||
ID: "9076804",
|
||||
AutoConnect: false,
|
||||
}},
|
||||
{LiveRoom: model.LiveRoom{
|
||||
ClientName: "bilibili",
|
||||
ID: "3819533",
|
||||
AutoConnect: false,
|
||||
}},
|
||||
},
|
||||
danmuCommands: make([]adapter.LiveRoomExecutor, 0),
|
||||
log: log,
|
||||
}
|
||||
config.LoadConfig(lr)
|
||||
lr.initialize()
|
||||
return lr
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) danmuCommandHandler(event *event.Event) {
|
||||
danmu := event.Data.(*model.DanmuMessage)
|
||||
args := strings.Split(danmu.Message, " ")
|
||||
if len(args[0]) == 0 {
|
||||
return
|
||||
}
|
||||
for _, cmd := range lr.danmuCommands {
|
||||
if cmd.Match(args[0]) {
|
||||
cmd.Execute(args[0], args[1:], danmu)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) initialize() {
|
||||
for i := 0; i < len(lr.liveRooms); i++ {
|
||||
if lr.liveRooms[i].client == nil {
|
||||
_ = lr.liveRooms[i].init(lr.danmuCommandHandler)
|
||||
}
|
||||
}
|
||||
go func() {
|
||||
for i := 0; i < len(lr.liveRooms); i++ {
|
||||
if lr.liveRooms[i].AutoConnect {
|
||||
lr.liveRooms[i].client.Connect()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) Name() string {
|
||||
return "LiveRooms"
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) Size() int {
|
||||
return len(lr.liveRooms)
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) OnLoad() {
|
||||
rooms := make([]model.LiveRoom, 0)
|
||||
_ = config.LoadJson(lr.LiveRoomPath, &lr.liveRooms)
|
||||
if len(rooms) == 0 {
|
||||
return
|
||||
}
|
||||
lr.liveRooms = make([]*liveRoomImpl, len(rooms))
|
||||
for i := 0; i < len(rooms); i++ {
|
||||
lr.liveRooms[i] = &liveRoomImpl{LiveRoom: rooms[i]}
|
||||
}
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) OnSave() {
|
||||
rooms := make([]model.LiveRoom, len(lr.liveRooms))
|
||||
for i := 0; i < len(lr.liveRooms); i++ {
|
||||
rooms[i] = lr.liveRooms[i].LiveRoom
|
||||
}
|
||||
_ = config.SaveJson(lr.LiveRoomPath, &rooms)
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) Get(index int) adapter.ILiveRoom {
|
||||
if index < 0 || index >= len(lr.liveRooms) {
|
||||
return nil
|
||||
}
|
||||
return lr.liveRooms[index]
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) GetRoomStatus(index int) bool {
|
||||
if index < 0 || index >= len(lr.liveRooms) {
|
||||
return false
|
||||
}
|
||||
return lr.liveRooms[index].client.Status()
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) Connect(index int) error {
|
||||
lr.log.Infof("[LiveRooms] Try to start LiveRooms.index=%d", index)
|
||||
if index < 0 || index >= len(lr.liveRooms) {
|
||||
lr.log.Errorf("[LiveRooms] LiveRooms.index=%d not found", index)
|
||||
return errors.New("index out of range")
|
||||
}
|
||||
lr.liveRooms[index].client.Connect()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) Disconnect(index int) error {
|
||||
lr.log.Infof("[LiveRooms] Try to Disconnect LiveRooms.index=%d", index)
|
||||
if index < 0 || index >= len(lr.liveRooms) {
|
||||
lr.log.Errorf("[LiveRooms] LiveRooms.index=%d not found", index)
|
||||
return errors.New("index out of range")
|
||||
}
|
||||
lr.liveRooms[index].client.Disconnect()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) AddRoom(clientName, roomId string) (*model.LiveRoom, error) {
|
||||
rm := &liveRoomImpl{
|
||||
LiveRoom: model.LiveRoom{
|
||||
ClientName: clientName,
|
||||
ID: roomId,
|
||||
AutoConnect: false,
|
||||
},
|
||||
}
|
||||
lr.log.Infof("[LiveRooms] add live room %s", &rm.LiveRoom)
|
||||
err := rm.init(lr.danmuCommandHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lr.log.Infof("[LiveRooms] %s init failed: %s", &rm.LiveRoom, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lr.liveRooms = append(lr.liveRooms, rm)
|
||||
return &rm.LiveRoom, nil
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) DeleteRoom(index int) error {
|
||||
lr.log.Infof("Try to remove LiveRooms.index=%d", index)
|
||||
if index < 0 || index >= len(lr.liveRooms) {
|
||||
lr.log.Warnf("LiveRooms.index=%d not found", index)
|
||||
return errors.New("index out of range")
|
||||
}
|
||||
if len(lr.liveRooms) == 1 {
|
||||
return errors.New("can't delete last room")
|
||||
}
|
||||
_ = lr.liveRooms[index].client.Disconnect()
|
||||
lr.liveRooms[index].Client().EventManager().UnregisterAll()
|
||||
lr.liveRooms = append(lr.liveRooms[:index], lr.liveRooms[index+1:]...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lr *LiveRoomController) AddDanmuCommand(executor adapter.LiveRoomExecutor) {
|
||||
lr.danmuCommands = append(lr.danmuCommands, executor)
|
||||
}
|
||||
30
internal/liveroom/config.go
Normal file
30
internal/liveroom/config.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package liveroom
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
)
|
||||
|
||||
type _cfg struct {
|
||||
ApiServer string
|
||||
LiveRoomPath string
|
||||
liveRooms []model.LiveRoom
|
||||
}
|
||||
|
||||
func (c *_cfg) Name() string {
|
||||
return "LiveRoom"
|
||||
}
|
||||
|
||||
func (c *_cfg) OnLoad() {
|
||||
_ = config.LoadJson(c.LiveRoomPath, &c.liveRooms)
|
||||
}
|
||||
|
||||
func (c *_cfg) OnSave() {
|
||||
_ = config.SaveJson(c.LiveRoomPath, &c.liveRooms)
|
||||
}
|
||||
|
||||
var cfg = &_cfg{
|
||||
ApiServer: "http://localhost:9090",
|
||||
LiveRoomPath: "liveroom.json",
|
||||
liveRooms: make([]model.LiveRoom, 0),
|
||||
}
|
||||
217
internal/liveroom/liveroom.go
Normal file
217
internal/liveroom/liveroom.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package liveroom
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/logger"
|
||||
"errors"
|
||||
liveroomsdk "github.com/AynaLivePlayer/liveroom-sdk"
|
||||
"github.com/AynaLivePlayer/liveroom-sdk/provider/openblive"
|
||||
)
|
||||
|
||||
type liveroom struct {
|
||||
room liveroomsdk.ILiveRoom
|
||||
model model.LiveRoom
|
||||
}
|
||||
|
||||
var liveRooms = map[string]*liveroom{}
|
||||
var log logger.ILogger
|
||||
|
||||
func Initialize() {
|
||||
log = global.Logger.WithPrefix("LiveRoom")
|
||||
config.LoadConfig(cfg)
|
||||
|
||||
liveroomsdk.RegisterProvider(openblive.NewOpenBLiveClientProvider(cfg.ApiServer, 1661006726438))
|
||||
// ignore web danmu client
|
||||
//liveroomsdk.RegisterProvider(webdm.NewWebDanmuClientProvider(cfg.ApiServer))
|
||||
|
||||
liveRooms = make(map[string]*liveroom, 0)
|
||||
|
||||
callEvents()
|
||||
registerHandlers()
|
||||
}
|
||||
|
||||
func StopAndSave() {
|
||||
log.Infof("Stop and save live rooms")
|
||||
for _, r := range liveRooms {
|
||||
log.Infof("Disconnect room %s: %v", r.room.Config().Identifier(), r.room.Disconnect())
|
||||
}
|
||||
liveroomConfigs := make([]model.LiveRoom, 0)
|
||||
for _, r := range liveRooms {
|
||||
liveroomConfigs = append(liveroomConfigs, r.model)
|
||||
}
|
||||
cfg.liveRooms = liveroomConfigs
|
||||
}
|
||||
|
||||
func addLiveRoom(roomModel model.LiveRoom) {
|
||||
log.Info("Add live room")
|
||||
room, err := liveroomsdk.CreateLiveRoom(roomModel.LiveRoom)
|
||||
if _, ok := liveRooms[room.Config().Identifier()]; ok {
|
||||
log.Errorf("fail to add, room %s already exists", room.Config().Identifier())
|
||||
global.EventManager.CallA(
|
||||
events.ErrorUpdate, events.ErrorUpdateEvent{
|
||||
Error: errors.New("room already exists"),
|
||||
})
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("Create live room failed: %s", err)
|
||||
global.EventManager.CallA(
|
||||
events.ErrorUpdate, events.ErrorUpdateEvent{
|
||||
Error: err,
|
||||
})
|
||||
return
|
||||
}
|
||||
lr := &liveroom{
|
||||
room: room,
|
||||
model: roomModel,
|
||||
}
|
||||
liveRooms[room.Config().Identifier()] = lr
|
||||
|
||||
room.OnStatusChange(func(connected bool) {
|
||||
log.Infof("room %s status change to %t", room.Config().Identifier(), connected)
|
||||
lr.model.Status = connected
|
||||
sendRoomStatusUpdateEvent(room.Config().Identifier())
|
||||
})
|
||||
room.OnMessage(func(message *liveroomsdk.Message) {
|
||||
log.Debugf("room %s receive message: %s", room.Config().Identifier(), message.Message)
|
||||
global.EventManager.CallA(
|
||||
events.LiveRoomMessageReceive,
|
||||
events.LiveRoomMessageReceiveEvent{
|
||||
Message: message,
|
||||
})
|
||||
})
|
||||
|
||||
log.Infof("success add live room %s", room.Config().Identifier())
|
||||
sendRoomsUpdateEvent()
|
||||
}
|
||||
|
||||
func registerHandlers() {
|
||||
global.EventManager.RegisterA(
|
||||
events.LiveRoomAddCmd, "internal.liveroom.add", func(event *event.Event) {
|
||||
data := event.Data.(events.LiveRoomAddCmdEvent)
|
||||
addLiveRoom(model.LiveRoom{
|
||||
LiveRoom: liveroomsdk.LiveRoom{
|
||||
Provider: data.Provider,
|
||||
Room: data.RoomKey,
|
||||
},
|
||||
Config: model.LiveRoomConfig{
|
||||
AutoConnect: false,
|
||||
},
|
||||
Title: data.Title,
|
||||
Status: false,
|
||||
})
|
||||
})
|
||||
|
||||
global.EventManager.RegisterA(
|
||||
events.LiveRoomRemoveCmd, "internal.liveroom.remove", func(event *event.Event) {
|
||||
data := event.Data.(events.LiveRoomRemoveCmdEvent)
|
||||
room, ok := liveRooms[data.Identifier]
|
||||
if !ok {
|
||||
log.Errorf("remove room failed, room %s not found", data.Identifier)
|
||||
return
|
||||
|
||||
}
|
||||
_ = room.room.Disconnect()
|
||||
room.room.OnStatusChange(nil)
|
||||
delete(liveRooms, data.Identifier)
|
||||
log.Infof("success remove live room %s", data.Identifier)
|
||||
sendRoomsUpdateEvent()
|
||||
})
|
||||
|
||||
global.EventManager.RegisterA(
|
||||
events.LiveRoomConfigChangeCmd, "internal.liveroom.config.change", func(event *event.Event) {
|
||||
data := event.Data.(events.LiveRoomConfigChangeCmdEvent)
|
||||
if room, ok := liveRooms[data.Identifier]; ok {
|
||||
room.model.Config = data.Config
|
||||
sendRoomStatusUpdateEvent(data.Identifier)
|
||||
}
|
||||
})
|
||||
|
||||
global.EventManager.RegisterA(
|
||||
events.LiveRoomOperationCmd, "internal.liveroom.operation", func(event *event.Event) {
|
||||
data := event.Data.(events.LiveRoomOperationCmdEvent)
|
||||
log.Infof("Live room operation SetConnect %v", data.SetConnect)
|
||||
room, ok := liveRooms[data.Identifier]
|
||||
if !ok {
|
||||
log.Errorf("Room %s not found", data.Identifier)
|
||||
return
|
||||
}
|
||||
var err error
|
||||
if data.SetConnect {
|
||||
err = room.room.Connect()
|
||||
} else {
|
||||
err = room.room.Disconnect()
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("Room %s operation failed: %s", data.Identifier, err)
|
||||
global.EventManager.CallA(
|
||||
events.ErrorUpdate, events.ErrorUpdateEvent{
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
global.EventManager.CallA(
|
||||
events.LiveRoomOperationFinish, events.LiveRoomOperationFinishEvent{})
|
||||
sendRoomStatusUpdateEvent(data.Identifier)
|
||||
})
|
||||
}
|
||||
|
||||
func sendRoomStatusUpdateEvent(roomId string) {
|
||||
room, ok := liveRooms[roomId]
|
||||
if !ok {
|
||||
log.Errorf("send room status update event failed, room %s not found", roomId)
|
||||
return
|
||||
}
|
||||
log.Infof("send room status update event, room %s", roomId)
|
||||
global.EventManager.CallA(
|
||||
events.LiveRoomStatusUpdate,
|
||||
events.LiveRoomStatusUpdateEvent{
|
||||
Room: room.model,
|
||||
})
|
||||
}
|
||||
|
||||
func sendRoomsUpdateEvent() {
|
||||
rooms := make([]model.LiveRoom, 0)
|
||||
for _, r := range liveRooms {
|
||||
rooms = append(rooms, r.model)
|
||||
}
|
||||
global.EventManager.CallA(
|
||||
events.LiveRoomRoomsUpdate,
|
||||
events.LiveRoomRoomsUpdateEvent{
|
||||
Rooms: rooms,
|
||||
})
|
||||
}
|
||||
|
||||
func callEvents() {
|
||||
providers := liveroomsdk.ListAvailableProviders()
|
||||
providerInfo := make([]model.LiveRoomProviderInfo, 0)
|
||||
for _, p := range providers {
|
||||
provider, _ := liveroomsdk.GetProvider(p)
|
||||
providerInfo = append(providerInfo, model.LiveRoomProviderInfo{
|
||||
Name: provider.GetName(),
|
||||
Description: provider.GetDescription(),
|
||||
})
|
||||
}
|
||||
for _, roomCfg := range cfg.liveRooms {
|
||||
addLiveRoom(roomCfg)
|
||||
}
|
||||
global.EventManager.CallA(
|
||||
events.LiveRoomProviderUpdate,
|
||||
events.LiveRoomProviderUpdateEvent{
|
||||
Providers: providerInfo,
|
||||
})
|
||||
sendRoomsUpdateEvent()
|
||||
for _, r := range liveRooms {
|
||||
if r.model.Config.AutoConnect {
|
||||
global.EventManager.CallA(
|
||||
events.LiveRoomOperationCmd,
|
||||
events.LiveRoomOperationCmdEvent{
|
||||
Identifier: r.room.Config().Identifier(),
|
||||
SetConnect: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
)
|
||||
|
||||
type LyricLoader struct {
|
||||
Lyric *model.Lyric
|
||||
Handler *event.Manager
|
||||
prev float64
|
||||
}
|
||||
|
||||
func NewLyricLoader() *LyricLoader {
|
||||
return &LyricLoader{
|
||||
Lyric: model.LoadLyric(""),
|
||||
Handler: event.MainManager.NewChildManager(),
|
||||
prev: -1,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LyricLoader) EventManager() *event.Manager {
|
||||
return l.Handler
|
||||
}
|
||||
|
||||
func (l *LyricLoader) Get() *model.Lyric {
|
||||
return l.Lyric
|
||||
}
|
||||
|
||||
func (l *LyricLoader) Reload(lyric string) {
|
||||
l.Lyric = model.LoadLyric(lyric)
|
||||
l.Handler.CallA(
|
||||
events.EventLyricReload,
|
||||
events.LyricReloadEvent{
|
||||
Lyrics: l.Lyric,
|
||||
})
|
||||
}
|
||||
|
||||
func (l *LyricLoader) Update(time float64) {
|
||||
lrc := l.Lyric.FindContext(time, 1, 3)
|
||||
if lrc == nil {
|
||||
return
|
||||
}
|
||||
if l.prev == lrc.Now.Time {
|
||||
return
|
||||
}
|
||||
l.prev = lrc.Now.Time
|
||||
l.Handler.CallA(
|
||||
events.EventLyricUpdate,
|
||||
events.LyricUpdateEvent{
|
||||
Lyrics: l.Lyric,
|
||||
Time: time,
|
||||
Lyric: lrc,
|
||||
})
|
||||
return
|
||||
}
|
||||
37
internal/player/mpv/config.go
Normal file
37
internal/player/mpv/config.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package mpv
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
)
|
||||
|
||||
type playerConfig struct {
|
||||
Volume float64
|
||||
}
|
||||
|
||||
func (p *playerConfig) Name() string {
|
||||
return "Player"
|
||||
}
|
||||
|
||||
func (p *playerConfig) OnLoad() {
|
||||
return
|
||||
}
|
||||
|
||||
func (p *playerConfig) OnSave() {
|
||||
return
|
||||
}
|
||||
|
||||
var cfg = &playerConfig{
|
||||
Volume: 100,
|
||||
}
|
||||
|
||||
func restoreConfig() {
|
||||
global.EventManager.CallA(events.PlayerVolumeChangeCmd, events.PlayerVolumeChangeCmdEvent{
|
||||
Volume: cfg.Volume,
|
||||
})
|
||||
global.EventManager.RegisterA(events.PlayerPropertyVolumeUpdate, "player.config.volume", func(evnt *event.Event) {
|
||||
data := evnt.Data.(events.PlayerPropertyVolumeUpdateEvent)
|
||||
cfg.Volume = data.Volume
|
||||
})
|
||||
}
|
||||
272
internal/player/mpv/mpv.go
Normal file
272
internal/player/mpv/mpv.go
Normal file
@@ -0,0 +1,272 @@
|
||||
package mpv
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/util"
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"AynaLivePlayer/pkg/logger"
|
||||
"fmt"
|
||||
"github.com/AynaLivePlayer/miaosic"
|
||||
"github.com/aynakeya/go-mpv"
|
||||
)
|
||||
|
||||
var running bool = false
|
||||
var libmpv *mpv.Mpv = nil
|
||||
var log logger.ILogger = nil
|
||||
|
||||
func SetupPlayer() {
|
||||
running = true
|
||||
config.LoadConfig(cfg)
|
||||
libmpv = mpv.Create()
|
||||
log = global.Logger.WithPrefix("MPV Player")
|
||||
err := libmpv.Initialize()
|
||||
if err != nil {
|
||||
log.Error("initialize libmpv failed")
|
||||
return
|
||||
}
|
||||
_ = libmpv.SetOptionString("vo", "null")
|
||||
log.Info("initialize libmpv success")
|
||||
registerHandler()
|
||||
registerCmdHandler()
|
||||
restoreConfig()
|
||||
log.Info("starting mpv player")
|
||||
go func() {
|
||||
for running {
|
||||
e := libmpv.WaitEvent(1)
|
||||
if e == nil {
|
||||
log.Warn("[MPV Player] event loop got nil event")
|
||||
}
|
||||
if e.EventId == mpv.EVENT_PROPERTY_CHANGE {
|
||||
eventProperty := e.Property()
|
||||
handler, ok := mpvPropertyHandler[eventProperty.Name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
var value interface{} = nil
|
||||
if eventProperty.Data != nil {
|
||||
value = eventProperty.Data.(mpv.Node).Value
|
||||
}
|
||||
//log.Debugf("[MPV Player] property update %s %v", eventProperty.Name, value)
|
||||
handler(value)
|
||||
}
|
||||
if e.EventId == mpv.EVENT_SHUTDOWN {
|
||||
log.Info("[MPV Player] libmpv shutdown")
|
||||
StopPlayer()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func StopPlayer() {
|
||||
log.Info("stopping mpv player")
|
||||
running = false
|
||||
// stop player async, should be closed in the end anyway
|
||||
go libmpv.TerminateDestroy()
|
||||
log.Info("mpv player stopped")
|
||||
}
|
||||
|
||||
var mpvPropertyHandler = map[string]func(value any){
|
||||
"pause": func(value any) {
|
||||
var data events.PlayerPropertyPauseUpdateEvent
|
||||
log.Debugf("pause property update %v %T", value, value)
|
||||
data.Paused = value.(bool)
|
||||
global.EventManager.CallA(events.PlayerPropertyPauseUpdate, data)
|
||||
},
|
||||
"percent-pos": func(value any) {
|
||||
var data events.PlayerPropertyPercentPosUpdateEvent
|
||||
if value == nil {
|
||||
data.PercentPos = 0
|
||||
} else {
|
||||
data.PercentPos = value.(float64)
|
||||
}
|
||||
// ignore bug value
|
||||
if data.PercentPos < 0.1 {
|
||||
return
|
||||
}
|
||||
global.EventManager.CallA(events.PlayerPropertyPercentPosUpdate, data)
|
||||
|
||||
},
|
||||
"idle-active": func(value any) {
|
||||
var data events.PlayerPropertyIdleActiveUpdateEvent
|
||||
if value == nil {
|
||||
data.IsIdle = true
|
||||
} else {
|
||||
data.IsIdle = value.(bool)
|
||||
}
|
||||
// if is idle, remove playing media
|
||||
if data.IsIdle {
|
||||
global.EventManager.CallA(events.PlayerPlayingUpdate, events.PlayerPlayingUpdateEvent{
|
||||
Media: model.Media{},
|
||||
Removed: true,
|
||||
})
|
||||
}
|
||||
global.EventManager.CallA(events.PlayerPropertyIdleActiveUpdate, data)
|
||||
|
||||
},
|
||||
"time-pos": func(value any) {
|
||||
var data events.PlayerPropertyTimePosUpdateEvent
|
||||
if value == nil {
|
||||
data.TimePos = 0
|
||||
} else {
|
||||
data.TimePos = value.(float64)
|
||||
}
|
||||
// ignore bug value
|
||||
if data.TimePos < 0.1 {
|
||||
return
|
||||
}
|
||||
global.EventManager.CallA(events.PlayerPropertyTimePosUpdate, data)
|
||||
},
|
||||
"duration": func(value any) {
|
||||
var data events.PlayerPropertyDurationUpdateEvent
|
||||
if value == nil {
|
||||
data.Duration = 0
|
||||
} else {
|
||||
data.Duration = value.(float64)
|
||||
}
|
||||
global.EventManager.CallA(events.PlayerPropertyDurationUpdate, data)
|
||||
},
|
||||
"volume": func(value any) {
|
||||
var data events.PlayerPropertyVolumeUpdateEvent
|
||||
if value == nil {
|
||||
data.Volume = 0
|
||||
} else {
|
||||
data.Volume = value.(float64)
|
||||
}
|
||||
global.EventManager.CallA(events.PlayerPropertyVolumeUpdate, data)
|
||||
},
|
||||
}
|
||||
|
||||
func registerHandler() {
|
||||
var err error
|
||||
for property, _ := range mpvPropertyHandler {
|
||||
log.Infof("register handler for mpv property %s", property)
|
||||
err = libmpv.ObserveProperty(util.Hash64(property), property, mpv.FORMAT_NODE)
|
||||
if err != nil {
|
||||
log.Errorf("register handler for mpv property %s failed: %s", property, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func registerCmdHandler() {
|
||||
global.EventManager.RegisterA(events.PlayerPlayCmd, "player.play", func(evnt *event.Event) {
|
||||
mediaInfo := evnt.Data.(events.PlayerPlayCmdEvent).Media.Info
|
||||
log.Infof("[MPV Player] Play media %s", mediaInfo.Title)
|
||||
mediaUrls, err := miaosic.GetMediaUrl(mediaInfo.Meta, miaosic.QualityAny)
|
||||
if err != nil || len(mediaUrls) == 0 {
|
||||
log.Warn("[MPV PlayControl] get media url failed", err)
|
||||
return
|
||||
}
|
||||
mediaUrl := mediaUrls[0]
|
||||
if val, ok := mediaUrl.Header["User-Agent"]; ok {
|
||||
log.Debug("[MPV PlayControl] set user-agent for mpv player")
|
||||
err := libmpv.SetPropertyString("user-agent", val)
|
||||
if err != nil {
|
||||
log.Warn("[MPV PlayControl] set player user-agent failed", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := mediaUrl.Header["Referer"]; ok {
|
||||
log.Debug("[MPV PlayControl] set referrer for mpv player")
|
||||
err := libmpv.SetPropertyString("referrer", val)
|
||||
if err != nil {
|
||||
log.Warn("[MPV PlayControl] set player referrer failed", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
log.Debugf("mpv command load file %s %s", mediaInfo.Title, mediaUrl.Url)
|
||||
if err := libmpv.Command([]string{"loadfile", mediaUrl.Url}); err != nil {
|
||||
log.Warn("[MPV PlayControl] mpv load media failed", mediaInfo)
|
||||
return
|
||||
}
|
||||
media := evnt.Data.(events.PlayerPlayCmdEvent).Media
|
||||
if m, err := miaosic.GetMediaInfo(media.Info.Meta); err == nil {
|
||||
media.Info = m
|
||||
}
|
||||
global.EventManager.CallA(events.PlayerPlayingUpdate, events.PlayerPlayingUpdateEvent{
|
||||
Media: media,
|
||||
Removed: false,
|
||||
})
|
||||
})
|
||||
global.EventManager.RegisterA(events.PlayerToggleCmd, "player.toggle", func(evnt *event.Event) {
|
||||
property, err := libmpv.GetProperty("pause", mpv.FORMAT_FLAG)
|
||||
if err != nil {
|
||||
log.Warn("[MPV PlayControl] get property pause failed", err)
|
||||
return
|
||||
}
|
||||
err = libmpv.SetProperty("pause", mpv.FORMAT_FLAG, !property.(bool))
|
||||
if err != nil {
|
||||
log.Warn("[MPV PlayControl] toggle pause failed", err)
|
||||
}
|
||||
})
|
||||
global.EventManager.RegisterA(events.PlayerSeekCmd, "player.seek", func(evnt *event.Event) {
|
||||
data := evnt.Data.(events.PlayerSeekCmdEvent)
|
||||
log.Debugf("seek to %f (absolute=%t)", data.Position, data.Absolute)
|
||||
var err error
|
||||
if data.Absolute {
|
||||
err = libmpv.SetProperty("time-pos", mpv.FORMAT_DOUBLE, data.Position)
|
||||
} else {
|
||||
err = libmpv.SetProperty("percent-pos", mpv.FORMAT_DOUBLE, data.Position)
|
||||
}
|
||||
if err != nil {
|
||||
log.Warn("seek failed", err)
|
||||
}
|
||||
})
|
||||
global.EventManager.RegisterA(events.PlayerVolumeChangeCmd, "player.volume", func(evnt *event.Event) {
|
||||
data := evnt.Data.(events.PlayerVolumeChangeCmdEvent)
|
||||
err := libmpv.SetProperty("volume", mpv.FORMAT_DOUBLE, data.Volume)
|
||||
if err != nil {
|
||||
log.Warn("set volume failed", err)
|
||||
}
|
||||
})
|
||||
|
||||
global.EventManager.RegisterA(events.PlayerVideoPlayerSetWindowHandleCmd, "player.next", func(evnt *event.Event) {
|
||||
handle := evnt.Data.(events.PlayerVideoPlayerSetWindowHandleCmdEvent).Handle
|
||||
err := SetWindowHandle(handle)
|
||||
if err != nil {
|
||||
log.Warn("set window handle failed", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func SetWindowHandle(handle uintptr) error {
|
||||
log.Infof("set window handle %d", handle)
|
||||
_ = libmpv.SetOptionString("wid", fmt.Sprintf("%d", handle))
|
||||
return libmpv.SetOptionString("vo", "gpu")
|
||||
}
|
||||
|
||||
//func (p *MpvPlayer) IsIdle() bool {
|
||||
// property, err := p.libmpv.GetProperty("idle-active", mpv.FORMAT_FLAG)
|
||||
// if err != nil {
|
||||
// p.log.Warn("[MPV Player] get property idle-active failed", err)
|
||||
// return false
|
||||
// }
|
||||
// return property.(bool)
|
||||
//}
|
||||
|
||||
//// GetAudioDeviceList get output device for mpv
|
||||
//// return format is []AudioDevice
|
||||
//func (p *MpvPlayer) GetAudioDeviceList() ([]model.AudioDevice, error) {
|
||||
// p.log.Debugf("[MPV Player] getting audio device list for mpv")
|
||||
// property, err := p.libmpv.GetProperty("audio-device-list", mpv.FORMAT_STRING)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// dl := make([]model.AudioDevice, 0)
|
||||
// gjson.Parse(property.(string)).ForEach(func(key, value gjson.Result) bool {
|
||||
// dl = append(dl, model.AudioDevice{
|
||||
// Name: value.Get("name").String(),
|
||||
// Description: value.Get("description").String(),
|
||||
// })
|
||||
// return true
|
||||
// })
|
||||
// return dl, nil
|
||||
//}
|
||||
//
|
||||
//func (p *MpvPlayer) SetAudioDevice(device string) error {
|
||||
// p.log.Debugf("[MPV Player] set audio device %s for mpv", device)
|
||||
// return p.libmpv.SetPropertyString("audio-device", device)
|
||||
//}
|
||||
11
internal/player/player.go
Normal file
11
internal/player/player.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package player
|
||||
|
||||
import "AynaLivePlayer/internal/player/mpv"
|
||||
|
||||
func SetupMpvPlayer() {
|
||||
mpv.SetupPlayer()
|
||||
}
|
||||
|
||||
func StopMpvPlayer() {
|
||||
mpv.StopPlayer()
|
||||
}
|
||||
167
internal/playlist/model.go
Normal file
167
internal/playlist/model.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package playlist
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"math/rand"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type playlist struct {
|
||||
Index int
|
||||
playlistId model.PlaylistID
|
||||
mode model.PlaylistMode
|
||||
Medias []model.Media
|
||||
Lock sync.RWMutex
|
||||
}
|
||||
|
||||
func newPlaylist(id model.PlaylistID) *playlist {
|
||||
pl := &playlist{
|
||||
playlistId: id,
|
||||
Medias: make([]model.Media, 0),
|
||||
Lock: sync.RWMutex{},
|
||||
Index: 0,
|
||||
}
|
||||
global.EventManager.RegisterA(events.PlaylistMoveCmd(id), "internal.playlist.move", func(event *event.Event) {
|
||||
e := event.Data.(events.PlaylistMoveCmdEvent)
|
||||
pl.Move(e.From, e.To)
|
||||
})
|
||||
global.EventManager.RegisterA(events.PlaylistInsertCmd(id), "internal.playlist.insert", func(event *event.Event) {
|
||||
e := event.Data.(events.PlaylistInsertCmdEvent)
|
||||
pl.Insert(e.Position, e.Media)
|
||||
})
|
||||
global.EventManager.RegisterA(events.PlaylistDeleteCmd(id), "internal.playlist.delete", func(event *event.Event) {
|
||||
e := event.Data.(events.PlaylistDeleteCmdEvent)
|
||||
pl.Delete(e.Index)
|
||||
})
|
||||
global.EventManager.RegisterA(events.PlaylistNextCmd(id), "internal.playlist.next", func(event *event.Event) {
|
||||
pl.Next(event.Data.(events.PlaylistNextCmdEvent).Remove)
|
||||
})
|
||||
global.EventManager.RegisterA(events.PlaylistModeChangeCmd(id), "internal.playlist.mode", func(event *event.Event) {
|
||||
pl.mode = event.Data.(events.PlaylistModeChangeUpdateEvent).Mode
|
||||
global.EventManager.CallA(events.PlaylistModeChangeUpdate(id), events.PlaylistModeChangeUpdateEvent{
|
||||
Mode: pl.mode,
|
||||
})
|
||||
})
|
||||
return pl
|
||||
}
|
||||
|
||||
func (p *playlist) CopyMedia() []model.Media {
|
||||
medias := make([]model.Media, len(p.Medias))
|
||||
copy(medias, p.Medias)
|
||||
return medias
|
||||
}
|
||||
|
||||
func (p *playlist) Size() int {
|
||||
return len(p.Medias)
|
||||
}
|
||||
|
||||
func (p *playlist) Replace(medias []model.Media) {
|
||||
p.Lock.Lock()
|
||||
p.Medias = medias
|
||||
p.Index = 0
|
||||
p.Lock.Unlock()
|
||||
global.EventManager.CallA(events.PlaylistDetailUpdate(p.playlistId), events.PlaylistDetailUpdateEvent{
|
||||
Medias: p.CopyMedia(),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *playlist) Insert(index int, media model.Media) {
|
||||
p.Lock.Lock()
|
||||
if index > p.Size() {
|
||||
index = p.Size()
|
||||
}
|
||||
if index < 0 {
|
||||
index = p.Size() + index + 1
|
||||
}
|
||||
p.Medias = append(p.Medias, model.Media{})
|
||||
for i := p.Size() - 1; i > index; i-- {
|
||||
p.Medias[i] = p.Medias[i-1]
|
||||
}
|
||||
p.Medias[index] = media
|
||||
p.Lock.Unlock()
|
||||
global.EventManager.CallA(events.PlaylistInsertUpdate(p.playlistId), events.PlaylistInsertUpdateEvent{
|
||||
Position: index,
|
||||
Media: media,
|
||||
})
|
||||
global.EventManager.CallA(events.PlaylistDetailUpdate(p.playlistId), events.PlaylistDetailUpdateEvent{
|
||||
Medias: p.CopyMedia(),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *playlist) Delete(index int) {
|
||||
p.Lock.Lock()
|
||||
if index >= p.Size() || index < 0 {
|
||||
p.Lock.Unlock()
|
||||
return
|
||||
}
|
||||
// todo: @5 delete optimization
|
||||
p.Medias = append(p.Medias[:index], p.Medias[index+1:]...)
|
||||
p.Lock.Unlock()
|
||||
global.EventManager.CallA(events.PlaylistDetailUpdate(p.playlistId), events.PlaylistDetailUpdateEvent{
|
||||
Medias: p.CopyMedia(),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *playlist) Move(src int, dst int) {
|
||||
if src >= p.Size() || src < 0 {
|
||||
return
|
||||
}
|
||||
p.Lock.Lock()
|
||||
if dst >= p.Size() {
|
||||
dst = p.Size() - 1
|
||||
}
|
||||
if dst < 0 {
|
||||
dst = 0
|
||||
}
|
||||
if dst == src {
|
||||
p.Lock.Unlock()
|
||||
return
|
||||
}
|
||||
step := 1
|
||||
if dst < src {
|
||||
step = -1
|
||||
}
|
||||
tmp := p.Medias[src]
|
||||
for i := src; i != dst; i += step {
|
||||
p.Medias[i] = p.Medias[i+step]
|
||||
}
|
||||
p.Medias[dst] = tmp
|
||||
p.Lock.Unlock()
|
||||
global.EventManager.CallA(events.PlaylistDetailUpdate(p.playlistId), events.PlaylistDetailUpdateEvent{
|
||||
Medias: p.CopyMedia(),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *playlist) Next(delete bool) {
|
||||
if p.Size() == 0 {
|
||||
// no media in the playlist
|
||||
// do not issue any event
|
||||
return
|
||||
}
|
||||
var index int
|
||||
index = p.Index
|
||||
if p.mode == model.PlaylistModeRandom {
|
||||
p.Index = rand.Intn(p.Size())
|
||||
} else if p.mode == model.PlaylistModeNormal {
|
||||
p.Index = (p.Index + 1) % p.Size()
|
||||
} else {
|
||||
p.Index = index
|
||||
}
|
||||
m := p.Medias[index]
|
||||
global.EventManager.CallA(events.PlaylistNextUpdate(p.playlistId), events.PlaylistNextUpdateEvent{
|
||||
Media: m,
|
||||
})
|
||||
if delete {
|
||||
p.Delete(index)
|
||||
if p.mode == model.PlaylistModeRandom {
|
||||
p.Index = rand.Intn(p.Size())
|
||||
} else if p.mode == model.PlaylistModeNormal {
|
||||
p.Index = index
|
||||
} else {
|
||||
p.Index = index
|
||||
}
|
||||
}
|
||||
}
|
||||
48
internal/playlist/playlist.go
Normal file
48
internal/playlist/playlist.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package playlist
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
)
|
||||
|
||||
var PlayerPlaylist *playlist = nil
|
||||
var HistoryPlaylist *playlist = nil
|
||||
var SystemPlaylist *playlist = nil
|
||||
var PlaylistsPlaylist *playlist = nil
|
||||
|
||||
type playlistConfig struct {
|
||||
SystemPlaylistMode model.PlaylistMode
|
||||
}
|
||||
|
||||
func (p *playlistConfig) Name() string {
|
||||
return "playlist"
|
||||
}
|
||||
|
||||
func (p *playlistConfig) OnLoad() {
|
||||
return
|
||||
}
|
||||
|
||||
func (p *playlistConfig) OnSave() {
|
||||
return
|
||||
}
|
||||
|
||||
var cfg = &playlistConfig{}
|
||||
|
||||
func Initialize() {
|
||||
PlayerPlaylist = newPlaylist(model.PlaylistIDPlayer)
|
||||
SystemPlaylist = newPlaylist(model.PlaylistIDSystem)
|
||||
HistoryPlaylist = newPlaylist(model.PlaylistIDHistory)
|
||||
PlaylistsPlaylist = newPlaylist(model.PlaylistIDPlaylists)
|
||||
config.LoadConfig(cfg)
|
||||
|
||||
global.EventManager.RegisterA(events.PlaylistModeChangeCmd(model.PlaylistIDSystem), "internal.playlist.system_init", func(event *event.Event) {
|
||||
cfg.SystemPlaylistMode = event.Data.(events.PlaylistModeChangeUpdateEvent).Mode
|
||||
})
|
||||
|
||||
global.EventManager.CallA(events.PlaylistModeChangeUpdate(model.PlaylistIDSystem), events.PlaylistModeChangeUpdateEvent{
|
||||
Mode: cfg.SystemPlaylistMode,
|
||||
})
|
||||
}
|
||||
3
internal/playlist/playlists.go
Normal file
3
internal/playlist/playlists.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package playlist
|
||||
|
||||
// todo: implement the playlist controller
|
||||
@@ -1,8 +1,8 @@
|
||||
package internal
|
||||
package todo
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/common/config"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-resty/resty/v2"
|
||||
@@ -1,12 +1,12 @@
|
||||
package internal
|
||||
package todo
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/adapters/provider"
|
||||
"AynaLivePlayer/common/config"
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/core/adapter"
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
event2 "AynaLivePlayer/pkg/event"
|
||||
"errors"
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ type PlayController struct {
|
||||
AudioDevice string
|
||||
Volume float64
|
||||
Cfg adapter.IPlayControlConfig
|
||||
eventManager *event.Manager `ini:"-"`
|
||||
eventManager *event2.Manager `ini:"-"`
|
||||
player adapter.IPlayer `ini:"-"`
|
||||
playlist adapter.IPlaylistController `ini:"-"`
|
||||
provider adapter.IProviderController `ini:"-"`
|
||||
@@ -46,7 +46,7 @@ func NewPlayerController(
|
||||
provider adapter.IProviderController,
|
||||
log adapter.ILogger) adapter.IPlayController {
|
||||
pc := &PlayController{
|
||||
eventManager: event.MainManager.NewChildManager(),
|
||||
eventManager: event2.MainManager.NewChildManager(),
|
||||
player: player,
|
||||
playlist: playlist,
|
||||
lyric: lyric,
|
||||
@@ -69,7 +69,7 @@ func NewPlayerController(
|
||||
return pc
|
||||
}
|
||||
|
||||
func (pc *PlayController) handleMpvIdlePlayNext(event *event.Event) {
|
||||
func (pc *PlayController) handleMpvIdlePlayNext(event *event2.Event) {
|
||||
isIdle := event.Data.(events.PlayerPropertyUpdateEvent).Value.(bool)
|
||||
if isIdle {
|
||||
pc.log.Info("[Controller] mpv went idle, try play next")
|
||||
@@ -77,7 +77,7 @@ func (pc *PlayController) handleMpvIdlePlayNext(event *event.Event) {
|
||||
}
|
||||
}
|
||||
|
||||
func (pc *PlayController) handlePlaylistAdd(event *event.Event) {
|
||||
func (pc *PlayController) handlePlaylistAdd(event *event2.Event) {
|
||||
if pc.player.IsIdle() {
|
||||
pc.PlayNext()
|
||||
return
|
||||
@@ -89,7 +89,7 @@ func (pc *PlayController) handlePlaylistAdd(event *event.Event) {
|
||||
}
|
||||
}
|
||||
|
||||
func (pc *PlayController) handleLyricUpdate(event *event.Event) {
|
||||
func (pc *PlayController) handleLyricUpdate(event *event2.Event) {
|
||||
data := event.Data.(events.PlayerPropertyUpdateEvent).Value
|
||||
if data == nil {
|
||||
return
|
||||
@@ -97,7 +97,7 @@ func (pc *PlayController) handleLyricUpdate(event *event.Event) {
|
||||
pc.lyric.Update(data.(float64))
|
||||
}
|
||||
|
||||
func (pc *PlayController) EventManager() *event.Manager {
|
||||
func (pc *PlayController) EventManager() *event2.Manager {
|
||||
return pc.eventManager
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
package internal
|
||||
package todo
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/adapters/provider"
|
||||
"AynaLivePlayer/common/config"
|
||||
"AynaLivePlayer/common/event"
|
||||
"AynaLivePlayer/core/adapter"
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/event"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type PlaylistController struct {
|
||||
@@ -214,221 +211,3 @@ func (pc *PlaylistController) PreparePlaylistByIndex(index int) error {
|
||||
}
|
||||
return pc.provider.PreparePlaylist(pc.Playlists[index])
|
||||
}
|
||||
|
||||
type playlistImpl struct {
|
||||
model.Playlist
|
||||
Index int
|
||||
Lock sync.RWMutex
|
||||
eventManager *event.Manager
|
||||
log adapter.ILogger
|
||||
}
|
||||
|
||||
func newPlaylistImpl(title string, em *event.Manager, log adapter.ILogger) adapter.IPlaylist {
|
||||
return &playlistImpl{
|
||||
Index: 0,
|
||||
Playlist: model.Playlist{
|
||||
Title: title,
|
||||
Medias: make([]*model.Media, 0),
|
||||
Mode: model.PlaylistModeNormal,
|
||||
Meta: model.Meta{},
|
||||
},
|
||||
eventManager: em,
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *playlistImpl) Model() *model.Playlist {
|
||||
return &p.Playlist
|
||||
}
|
||||
|
||||
func (p *playlistImpl) EventManager() *event.Manager {
|
||||
return p.eventManager
|
||||
}
|
||||
|
||||
func (p *playlistImpl) Identifier() string {
|
||||
return p.Playlist.Meta.Identifier()
|
||||
}
|
||||
|
||||
func (p *playlistImpl) DisplayName() string {
|
||||
if p.Playlist.Title != "" {
|
||||
return p.Playlist.Title
|
||||
}
|
||||
return p.Playlist.Meta.Identifier()
|
||||
}
|
||||
|
||||
func (p *playlistImpl) Size() int {
|
||||
return p.Playlist.Size()
|
||||
}
|
||||
|
||||
func (p *playlistImpl) Get(index int) *model.Media {
|
||||
if index < 0 || index >= p.Playlist.Size() {
|
||||
return nil
|
||||
}
|
||||
return p.Playlist.Medias[index]
|
||||
}
|
||||
|
||||
func (p *playlistImpl) Pop() *model.Media {
|
||||
p.log.Debugf("[Playlists] %s pop first media", p.Playlist)
|
||||
if p.Size() == 0 {
|
||||
return nil
|
||||
}
|
||||
p.Lock.Lock()
|
||||
index := 0
|
||||
if p.Mode == model.PlaylistModeRandom {
|
||||
index = rand.Intn(p.Size())
|
||||
}
|
||||
m := p.Medias[index]
|
||||
for i := index; i > 0; i-- {
|
||||
p.Medias[i] = p.Medias[i-1]
|
||||
}
|
||||
p.Medias = p.Medias[1:]
|
||||
p.Lock.Unlock()
|
||||
if m == nil {
|
||||
p.log.Warn("[Playlists] pop first media failed, no media left in the playlist")
|
||||
return nil
|
||||
}
|
||||
p.eventManager.CallA(
|
||||
events.EventPlaylistUpdate,
|
||||
events.PlaylistUpdateEvent{
|
||||
Playlist: p.Playlist.Copy(),
|
||||
},
|
||||
)
|
||||
return m
|
||||
}
|
||||
|
||||
func (p *playlistImpl) Replace(medias []*model.Media) {
|
||||
p.log.Infof("[Playlists] %s replace all media", &p.Playlist)
|
||||
p.Lock.Lock()
|
||||
p.Playlist.Medias = medias
|
||||
p.Index = 0
|
||||
p.Lock.Unlock()
|
||||
p.eventManager.CallA(
|
||||
events.EventPlaylistUpdate,
|
||||
events.PlaylistUpdateEvent{
|
||||
Playlist: p.Playlist.Copy(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (p *playlistImpl) Push(media *model.Media) {
|
||||
p.Insert(-1, media)
|
||||
}
|
||||
|
||||
func (p *playlistImpl) Insert(index int, media *model.Media) {
|
||||
p.log.Infof("[Playlists] insert media into new index %d at %s ", index, p.Playlist)
|
||||
p.log.Debugf("media=%s %v", media.Title, media.Meta)
|
||||
e := event.Event{
|
||||
Id: events.EventPlaylistPreInsert,
|
||||
Cancelled: false,
|
||||
Data: events.PlaylistInsertEvent{
|
||||
Playlist: p.Playlist.Copy(),
|
||||
Index: index,
|
||||
Media: media,
|
||||
},
|
||||
}
|
||||
p.eventManager.Call(&e)
|
||||
if e.Cancelled {
|
||||
p.log.Info("[Playlists] media insertion has been cancelled by handler")
|
||||
return
|
||||
}
|
||||
p.Lock.Lock()
|
||||
if index > p.Size() {
|
||||
index = p.Size()
|
||||
}
|
||||
if index < 0 {
|
||||
index = p.Size() + index + 1
|
||||
}
|
||||
p.Medias = append(p.Medias, nil)
|
||||
for i := p.Size() - 1; i > index; i-- {
|
||||
p.Medias[i] = p.Medias[i-1]
|
||||
}
|
||||
p.Medias[index] = media
|
||||
p.Lock.Unlock()
|
||||
p.eventManager.CallA(
|
||||
events.EventPlaylistUpdate,
|
||||
events.PlaylistUpdateEvent{
|
||||
Playlist: p.Playlist.Copy(),
|
||||
},
|
||||
)
|
||||
p.eventManager.CallA(
|
||||
events.EventPlaylistInsert,
|
||||
events.PlaylistInsertEvent{
|
||||
Playlist: p.Playlist.Copy(),
|
||||
Index: index,
|
||||
Media: media,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (p *playlistImpl) Delete(index int) *model.Media {
|
||||
p.log.Infof("from media at index %d from %s", index, p.Playlist)
|
||||
if index >= p.Size() || index < 0 {
|
||||
p.Lock.Unlock()
|
||||
return nil
|
||||
}
|
||||
m := p.Medias[index]
|
||||
p.Lock.Lock()
|
||||
// todo: @5 delete optimization
|
||||
p.Medias = append(p.Medias[:index], p.Medias[index+1:]...)
|
||||
p.Lock.Unlock()
|
||||
if m == nil {
|
||||
p.log.Warnf("[Playlists] media at index %d does not exist", index)
|
||||
}
|
||||
p.eventManager.CallA(
|
||||
events.EventPlaylistUpdate,
|
||||
events.PlaylistUpdateEvent{
|
||||
Playlist: p.Playlist.Copy(),
|
||||
})
|
||||
return m
|
||||
}
|
||||
|
||||
func (p *playlistImpl) Move(src int, dst int) {
|
||||
p.log.Infof("[Playlists] from media from index %d to %d", src, dst)
|
||||
if src >= p.Size() || src < 0 {
|
||||
p.log.Warnf("[Playlists] media at index %d does not exist", src)
|
||||
return
|
||||
}
|
||||
p.Lock.Lock()
|
||||
if dst >= p.Size() {
|
||||
dst = p.Size() - 1
|
||||
}
|
||||
if dst < 0 {
|
||||
dst = 0
|
||||
}
|
||||
if dst == src {
|
||||
p.Lock.Unlock()
|
||||
return
|
||||
}
|
||||
step := 1
|
||||
if dst < src {
|
||||
step = -1
|
||||
}
|
||||
tmp := p.Medias[src]
|
||||
for i := src; i != dst; i += step {
|
||||
p.Medias[i] = p.Medias[i+step]
|
||||
}
|
||||
p.Medias[dst] = tmp
|
||||
p.Lock.Unlock()
|
||||
p.eventManager.CallA(
|
||||
events.EventPlaylistUpdate,
|
||||
events.PlaylistUpdateEvent{
|
||||
Playlist: p.Playlist.Copy(),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *playlistImpl) Next() *model.Media {
|
||||
p.log.Infof("[Playlists] %s get next media with random=%t", p, p.Mode == model.PlaylistModeRandom)
|
||||
if p.Size() == 0 {
|
||||
p.log.Warn("[Playlists] get next media failed, no media left in the playlist")
|
||||
return nil
|
||||
}
|
||||
var index int
|
||||
index = p.Index
|
||||
if p.Mode == model.PlaylistModeRandom {
|
||||
p.Index = rand.Intn(p.Size())
|
||||
} else {
|
||||
p.Index = (p.Index + 1) % p.Size()
|
||||
}
|
||||
m := p.Medias[index]
|
||||
return m
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package internal
|
||||
package todo
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/adapter"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user