rewrite using IoC and DI

This commit is contained in:
Aynakeya
2022-12-23 05:06:57 -08:00
parent 0498d2dbf3
commit c47d338a9e
88 changed files with 2295 additions and 1856 deletions

View File

@@ -1,31 +0,0 @@
package controller
import (
"AynaLivePlayer/event"
"AynaLivePlayer/liveclient"
"strings"
)
var Commands []DanmuCommandExecutor
type DanmuCommandExecutor interface {
Match(command string) bool
Execute(command string, args []string, danmu *liveclient.DanmuMessage)
}
func AddCommand(executors ...DanmuCommandExecutor) {
Commands = append(Commands, executors...)
}
func danmuCommandHandler(event *event.Event) {
danmu := event.Data.(*liveclient.DanmuMessage)
args := strings.Split(danmu.Message, " ")
if len(args[0]) == 0 {
return
}
for _, cmd := range Commands {
if cmd.Match(args[0]) {
cmd.Execute(args[0], args[1:], danmu)
}
}
}

View File

@@ -1,9 +1,13 @@
package controller
import (
"AynaLivePlayer/logger"
)
var Instance IController = nil
const MODULE_CONTROLLER = "Controller"
var l = logger.Logger.WithField("Module", MODULE_CONTROLLER)
type IController interface {
LiveRooms() ILiveRoomController
PlayControl() IPlayController
Playlists() IPlaylistController
Provider() IProviderController
Plugin() IPluginController
LoadPlugins(plugins ...Plugin)
CloseAndSave()
}

View File

@@ -1,10 +0,0 @@
package controller
import (
"fmt"
"testing"
)
func TestController(t *testing.T) {
fmt.Println(LiveClient == nil)
}

View File

@@ -0,0 +1,59 @@
package core
import (
"AynaLivePlayer/common/logger"
"AynaLivePlayer/controller"
)
var lg = logger.Logger.WithField("Module", "CoreController")
type Controller struct {
liveroom controller.ILiveRoomController `ini:"-"`
player controller.IPlayController `ini:"-"`
lyric controller.ILyricLoader `ini:"-"`
playlist controller.IPlaylistController `ini:"-"`
provider controller.IProviderController `ini:"-"`
plugin controller.IPluginController `ini:"-"`
}
func NewController(
liveroom controller.ILiveRoomController, player controller.IPlayController,
playlist controller.IPlaylistController,
provider controller.IProviderController, plugin controller.IPluginController) controller.IController {
cc := &Controller{
liveroom: liveroom,
player: player,
playlist: playlist,
provider: provider,
plugin: plugin,
}
return cc
}
func (c *Controller) LiveRooms() controller.ILiveRoomController {
return c.liveroom
}
func (c *Controller) PlayControl() controller.IPlayController {
return c.player
}
func (c *Controller) Playlists() controller.IPlaylistController {
return c.playlist
}
func (c *Controller) Provider() controller.IProviderController {
return c.provider
}
func (c *Controller) Plugin() controller.IPluginController {
return c.plugin
}
func (c *Controller) LoadPlugins(plugins ...controller.Plugin) {
c.plugin.LoadPlugins(plugins...)
}
func (c *Controller) CloseAndSave() {
c.plugin.ClosePlugins()
}

201
controller/core/liveroom.go Normal file
View File

@@ -0,0 +1,201 @@
package core
import (
"AynaLivePlayer/common/event"
"AynaLivePlayer/config"
"AynaLivePlayer/controller"
"AynaLivePlayer/liveclient"
"AynaLivePlayer/model"
"errors"
"strings"
)
type coreLiveRoom struct {
model.LiveRoom
client liveclient.LiveClient
}
func (r *coreLiveRoom) Model() *model.LiveRoom {
return &r.LiveRoom
}
func (r *coreLiveRoom) Status() bool {
return r.client.Status()
}
func (r *coreLiveRoom) EventManager() *event.Manager {
return r.client.EventManager()
}
func (r *coreLiveRoom) init(msgHandler event.HandlerFunc) (err error) {
if r.client != nil {
return nil
}
r.client, err = liveclient.NewLiveClient(r.ClientName, r.ID)
if err != nil {
return
}
r.client.EventManager().RegisterA(
liveclient.EventMessageReceive,
"controller.danmu.command",
msgHandler)
return nil
}
type LiveRoomController struct {
LiveRoomPath string
liveRooms []*coreLiveRoom
danmuCommands []controller.DanmuCommandExecutor
}
func NewLiveRoomController() controller.ILiveRoomController {
lr := &LiveRoomController{
LiveRoomPath: "liverooms.json",
liveRooms: []*coreLiveRoom{
{LiveRoom: model.LiveRoom{
ClientName: "bilibili",
ID: "9076804",
AutoConnect: false,
}},
{LiveRoom: model.LiveRoom{
ClientName: "bilibili",
ID: "3819533",
AutoConnect: false,
}},
},
danmuCommands: make([]controller.DanmuCommandExecutor, 0),
}
config.LoadConfig(lr)
lr.initialize()
return lr
}
func (lr *LiveRoomController) danmuCommandHandler(event *event.Event) {
danmu := event.Data.(*liveclient.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([]*coreLiveRoom, len(rooms))
for i := 0; i < len(rooms); i++ {
lr.liveRooms[i] = &coreLiveRoom{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) controller.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 {
lg.Infof("[LiveRooms] Try to start LiveRooms.index=%d", index)
if index < 0 || index >= len(lr.liveRooms) {
lg.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 {
lg.Infof("[LiveRooms] Try to Disconnect LiveRooms.index=%d", index)
if index < 0 || index >= len(lr.liveRooms) {
lg.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 := &coreLiveRoom{
LiveRoom: model.LiveRoom{
ClientName: clientName,
ID: roomId,
AutoConnect: false,
},
}
lg.Infof("[LiveRooms] add live room %s", &rm.LiveRoom)
err := rm.init(lr.danmuCommandHandler)
if err != nil {
return nil, err
}
lg.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 {
lg.Infof("Try to remove LiveRooms.index=%d", index)
if index < 0 || index >= len(lr.liveRooms) {
lg.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].EventManager().UnregisterAll()
lr.liveRooms = append(lr.liveRooms[:index], lr.liveRooms[index+1:]...)
return nil
}
func (lr *LiveRoomController) AddDanmuCommand(executor controller.DanmuCommandExecutor) {
lr.danmuCommands = append(lr.danmuCommands, executor)
}

56
controller/core/lyric.go Normal file
View File

@@ -0,0 +1,56 @@
package core
import (
"AynaLivePlayer/common/event"
"AynaLivePlayer/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(
model.EventLyricReload,
model.LyricReloadEvent{
Lyrics: l.Lyric,
})
}
func (l *LyricLoader) Update(time float64) {
lrc := l.Lyric.Find(time)
if lrc == nil {
return
}
if l.prev == lrc.Time {
return
}
l.prev = lrc.Time
l.Handler.CallA(
model.EventLyricUpdate,
model.LyricUpdateEvent{
Lyrics: l.Lyric,
Time: time,
Lyric: lrc,
})
return
}

View File

@@ -0,0 +1,242 @@
package core
import (
"AynaLivePlayer/common/event"
"AynaLivePlayer/config"
"AynaLivePlayer/controller"
"AynaLivePlayer/model"
"AynaLivePlayer/player"
"AynaLivePlayer/provider"
)
type PlayController struct {
eventManager *event.Manager `ini:"-"`
player player.IPlayer `ini:"-"`
playlist controller.IPlaylistController `ini:"-"`
provider controller.IProviderController `ini:"-"`
lyric controller.ILyricLoader `ini:"-"`
playing *model.Media `ini:"-"`
AudioDevice string
Volume float64
SkipPlaylist bool
}
func (pc *PlayController) GetSkipPlaylist() bool {
return pc.SkipPlaylist
}
func (pc *PlayController) SetSkipPlaylist(b bool) {
pc.SkipPlaylist = b
}
func (pc *PlayController) Name() string {
return "PlayController"
}
func (pc *PlayController) OnLoad() {
return
}
func (pc *PlayController) OnSave() {
return
}
func NewPlayerController(
player player.IPlayer,
playlist controller.IPlaylistController,
lyric controller.ILyricLoader,
provider controller.IProviderController) controller.IPlayController {
pc := &PlayController{
eventManager: event.MainManager.NewChildManager(),
player: player,
playlist: playlist,
lyric: lyric,
provider: provider,
playing: &model.Media{},
AudioDevice: "auto",
Volume: 100,
SkipPlaylist: false,
}
config.LoadConfig(pc)
pc.SetVolume(pc.Volume)
pc.SetAudioDevice(pc.AudioDevice)
pc.player.ObserveProperty(model.PlayerPropIdleActive, "controller.playcontrol.idleplaynext", pc.handleMpvIdlePlayNext)
pc.playlist.GetCurrent().EventManager().RegisterA(model.EventPlaylistInsert, "controller.playcontrol.playlistadd", pc.handlePlaylistAdd)
pc.player.ObserveProperty(model.PlayerPropTimePos, "controller.playcontrol.updatelyric", pc.handleLyricUpdate)
return pc
}
func (pc *PlayController) handleMpvIdlePlayNext(event *event.Event) {
isIdle := event.Data.(model.PlayerPropertyUpdateEvent).Value.(bool)
if isIdle {
lg.Info("[Controller] mpv went idle, try play next")
pc.PlayNext()
}
}
func (pc *PlayController) handlePlaylistAdd(event *event.Event) {
if pc.player.IsIdle() {
pc.PlayNext()
return
}
lg.Debugf("[PlayController] playlist add event, SkipPlaylist=%t", pc.SkipPlaylist)
if pc.SkipPlaylist && pc.playing != nil && pc.playing.User == controller.PlaylistUser {
pc.PlayNext()
return
}
}
func (pc *PlayController) handleLyricUpdate(event *event.Event) {
data := event.Data.(model.PlayerPropertyUpdateEvent).Value
if data == nil {
return
}
pc.lyric.Update(data.(float64))
}
func (pc *PlayController) EventManager() *event.Manager {
return pc.eventManager
}
func (pc *PlayController) GetPlaying() *model.Media {
return pc.playing
}
func (pc *PlayController) GetPlayer() player.IPlayer {
return pc.player
}
func (pc *PlayController) GetLyric() controller.ILyricLoader {
return pc.lyric
}
func (pc *PlayController) PlayNext() {
lg.Infof("[PlayController] try to play next possible media")
if pc.playlist.GetCurrent().Size() == 0 && pc.playlist.GetDefault().Size() == 0 {
return
}
var media *model.Media
if pc.playlist.GetCurrent().Size() != 0 {
media = pc.playlist.GetCurrent().Pop().Copy()
} else if pc.playlist.GetDefault().Size() != 0 {
media = pc.playlist.GetDefault().Next().Copy()
media.User = controller.PlaylistUser
}
pc.Play(media)
}
func (pc *PlayController) Play(media *model.Media) {
lg.Infof("[PlayController] prepare media %s", media.Title)
err := pc.provider.PrepareMedia(media)
if err != nil {
lg.Warn("[PlayController] prepare media failed. try play next")
pc.PlayNext()
return
}
pc.playing = media
pc.playlist.AddToHistory(media)
if err := pc.player.Play(media); err != nil {
lg.Warn("[PlayController] play failed", err)
return
}
pc.eventManager.CallA(model.EventPlay, model.PlayEvent{
Media: media,
})
pc.lyric.Reload(media.Lyric)
// reset
media.Url = ""
}
func (pc *PlayController) Add(keyword string, user interface{}) {
media := pc.provider.MediaMatch(keyword)
if media == nil {
medias, err := pc.provider.Search(keyword)
if err != nil {
lg.Warnf("[PlayController] search for %s, got error %s", keyword, err)
return
}
if len(medias) == 0 {
lg.Info("[PlayController] search for %s, got no result", keyword)
return
}
media = medias[0]
}
media.User = user
lg.Infof("[PlayController] add media %s (%s)", media.Title, media.Artist)
pc.playlist.GetCurrent().Insert(-1, media)
}
func (pc *PlayController) AddWithProvider(keyword string, pname string, user interface{}) {
media := provider.MatchMedia(pname, keyword)
if media == nil {
medias, err := provider.Search(pname, keyword)
if err != nil {
lg.Warnf("[PlayController] search for %s, got error %s", keyword, err)
return
}
if len(medias) == 0 {
lg.Infof("[PlayController] search for %s, got no result", keyword)
return
}
media = medias[0]
}
media.User = user
lg.Infof("[PlayController] add media %s (%s)", media.Title, media.Artist)
pc.playlist.GetCurrent().Insert(-1, media)
}
func (pc *PlayController) Seek(position float64, absolute bool) {
if err := pc.player.Seek(position, absolute); err != nil {
lg.Warnf("[PlayController] seek to position %f (%t) failed, %s", position, absolute, err)
}
}
func (pc *PlayController) Toggle() (b bool) {
var err error
if pc.player.IsPaused() {
err = pc.player.Unpause()
b = false
} else {
err = pc.player.Pause()
b = true
}
if err != nil {
lg.Warn("[PlayController] toggle failed", err)
}
return
}
func (pc *PlayController) SetVolume(volume float64) {
if pc.player.SetVolume(volume) != nil {
lg.Warnf("[PlayController] set mpv volume to %f failed", volume)
return
}
pc.Volume = volume
}
func (pc *PlayController) Destroy() {
pc.player.Stop()
}
func (pc *PlayController) GetCurrentAudioDevice() string {
return pc.AudioDevice
}
func (pc *PlayController) GetAudioDevices() []model.AudioDevice {
dl, err := pc.player.GetAudioDeviceList()
if err != nil {
return make([]model.AudioDevice, 0)
}
return dl
}
func (pc *PlayController) SetAudioDevice(device string) {
lg.Infof("[PlayController] set audio device to %s", device)
if err := pc.player.SetAudioDevice(device); err != nil {
lg.Warnf("[PlayController] set mpv audio device to %s failed, %s", device, err)
_ = pc.player.SetAudioDevice("auto")
pc.AudioDevice = "auto"
return
}
pc.AudioDevice = device
}

397
controller/core/playlist.go Normal file
View File

@@ -0,0 +1,397 @@
package core
import (
"AynaLivePlayer/common/event"
"AynaLivePlayer/config"
"AynaLivePlayer/controller"
"AynaLivePlayer/model"
"AynaLivePlayer/provider"
"errors"
"fmt"
"math/rand"
"sync"
)
type PlaylistController struct {
PlaylistPath string
provider controller.IProviderController
History controller.IPlaylist `ini:"-"`
Current controller.IPlaylist `ini:"-"`
Default controller.IPlaylist `ini:"-"`
Playlists []controller.IPlaylist `ini:"-"`
DefaultIndex int
CurrentPlaylistRandom bool
DefaultPlaylistRandom bool
}
func NewPlaylistController(
provider controller.IProviderController) controller.IPlaylistController {
pc := &PlaylistController{
PlaylistPath: "playlist.json",
provider: provider,
History: NewPlaylist("history"),
Default: NewPlaylist("default"),
Current: NewPlaylist("current"),
Playlists: make([]controller.IPlaylist, 0),
DefaultIndex: 0,
CurrentPlaylistRandom: false,
DefaultPlaylistRandom: true,
}
config.LoadConfig(pc)
if pc.DefaultIndex < 0 || pc.DefaultIndex >= len(pc.Playlists) {
pc.DefaultIndex = 0
lg.Warn("playlist index did not find")
}
go func() {
_ = pc.SetDefault(pc.DefaultIndex)
}()
return pc
}
func (pc *PlaylistController) Name() string {
return "Playlists"
}
func (pc *PlaylistController) OnLoad() {
var metas = []model.Meta{
{
"netease",
"2382819181",
},
{"netease",
"4987059624",
},
{"local",
"list1",
},
}
_ = config.LoadJson(pc.PlaylistPath, &metas)
for _, m := range metas {
p := NewPlaylist(fmt.Sprintf("%s-%s", m.Name, m.Id))
p.Model().Meta = m
pc.Playlists = append(pc.Playlists, p)
}
if pc.CurrentPlaylistRandom {
pc.Current.Model().Mode = model.PlaylistModeRandom
}
if pc.DefaultPlaylistRandom {
pc.Default.Model().Mode = model.PlaylistModeRandom
}
}
func (pc *PlaylistController) OnSave() {
var metas = make([]model.Meta, 0)
for _, pl := range pc.Playlists {
metas = append(metas, pl.Model().Meta)
}
_ = config.SaveJson(pc.PlaylistPath, &metas)
if pc.Current.Model().Mode == model.PlaylistModeRandom {
pc.CurrentPlaylistRandom = true
} else {
pc.CurrentPlaylistRandom = false
}
if pc.Default.Model().Mode == model.PlaylistModeRandom {
pc.DefaultPlaylistRandom = true
} else {
pc.DefaultPlaylistRandom = false
}
}
func (pc *PlaylistController) Size() int {
return len(pc.Playlists)
}
func (pc *PlaylistController) GetHistory() controller.IPlaylist {
return pc.History
}
func (pc *PlaylistController) GetDefault() controller.IPlaylist {
return pc.Default
}
func (pc *PlaylistController) GetCurrent() controller.IPlaylist {
return pc.Current
}
func (pc *PlaylistController) AddToHistory(media *model.Media) {
lg.Tracef("add media %s (%s) to history", media.Title, media.Artist)
media = media.Copy()
// reset url for future use
media.Url = ""
if pc.History.Size() >= 1024 {
pc.History.Replace([]*model.Media{})
}
media.User = controller.HistoryUser
pc.History.Push(media)
return
}
func (pc *PlaylistController) Get(index int) controller.IPlaylist {
if index < 0 || index >= len(pc.Playlists) {
lg.Warnf("playlist.index=%d not found", index)
return nil
}
return pc.Playlists[index]
}
func (pc *PlaylistController) Add(pname string, uri string) controller.IPlaylist {
lg.Infof("try add playlist %s with provider %s", uri, pname)
id, err := provider.FormatPlaylistUrl(pname, uri)
if err != nil || id == "" {
lg.Warnf("fail to format %s playlist id for %s", uri, pname)
return nil
}
p := NewPlaylist(fmt.Sprintf("%s-%s", pname, id))
p.Model().Meta = model.Meta{
Name: pname,
Id: id,
}
pc.Playlists = append(pc.Playlists, p)
return p
}
func (pc *PlaylistController) Remove(index int) controller.IPlaylist {
lg.Infof("Try to remove playlist.index=%d", index)
if index < 0 || index >= len(pc.Playlists) {
lg.Warnf("playlist.index=%d not found", index)
return nil
}
if index == pc.DefaultIndex {
lg.Info("Delete current system playlist, reset system playlist to index = 0")
_ = pc.SetDefault(0)
}
if index < pc.DefaultIndex {
lg.Debugf("Delete playlist before system playlist (index=%d), reduce system playlist index by 1", pc.DefaultIndex)
pc.DefaultIndex = pc.DefaultIndex - 1
}
pl := pc.Playlists[index]
pc.Playlists = append(pc.Playlists[:index], pc.Playlists[index+1:]...)
return pl
}
func (pc *PlaylistController) SetDefault(index int) error {
lg.Infof("try set system playlist to playlist.id=%d", index)
if index < 0 || index >= len(pc.Playlists) {
lg.Warn("playlist.index=%d not found", index)
return errors.New("playlist.index not found")
}
err := pc.provider.PreparePlaylist(pc.Playlists[index])
if err != nil {
return err
}
pl := pc.Playlists[index].Model().Copy()
pc.DefaultIndex = index
controller.ApplyUser(pl.Medias, controller.PlaylistUser)
pc.Default.Replace(pl.Medias)
pc.Default.Model().Name = pl.Name
return nil
}
func (pc *PlaylistController) PreparePlaylistByIndex(index int) error {
lg.Infof("try prepare playlist.id=%d", index)
if index < 0 || index >= len(pc.Playlists) {
lg.Warn("playlist.id=%d not found", index)
return nil
}
return pc.provider.PreparePlaylist(pc.Playlists[index])
}
type corePlaylist struct {
model.Playlist
Index int
Lock sync.RWMutex
eventManager *event.Manager
}
func NewPlaylist(name string) controller.IPlaylist {
return &corePlaylist{
Index: 0,
Playlist: model.Playlist{
Name: name,
Medias: make([]*model.Media, 0),
Mode: model.PlaylistModeNormal,
Meta: model.Meta{},
},
eventManager: event.MainManager.NewChildManager(),
}
}
func (p *corePlaylist) Model() *model.Playlist {
return &p.Playlist
}
func (p *corePlaylist) EventManager() *event.Manager {
return p.eventManager
}
func (p *corePlaylist) Name() string {
return p.Playlist.Name
}
func (p *corePlaylist) Size() int {
return p.Playlist.Size()
}
func (p *corePlaylist) Get(index int) *model.Media {
if index < 0 || index >= p.Playlist.Size() {
return nil
}
return p.Playlist.Medias[index]
}
func (p *corePlaylist) Pop() *model.Media {
lg.Info("[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 {
lg.Warn("[Playlists] pop first media failed, no media left in the playlist")
return nil
}
p.eventManager.CallA(
model.EventPlaylistUpdate,
model.PlaylistUpdateEvent{Playlist: p.Playlist.Copy()},
)
return m
}
func (p *corePlaylist) Replace(medias []*model.Media) {
lg.Infof("[Playlists] %s replace all media", &p.Playlist)
p.Lock.Lock()
p.Playlist.Medias = medias
p.Index = 0
p.Lock.Unlock()
p.eventManager.CallA(
model.EventPlaylistUpdate,
model.PlaylistUpdateEvent{Playlist: p.Playlist.Copy()},
)
}
func (p *corePlaylist) Push(media *model.Media) {
p.Insert(-1, media)
}
func (p *corePlaylist) Insert(index int, media *model.Media) {
lg.Infof("[Playlists]insert media into new index %d at %s ", index, p.Playlist)
lg.Debugf("media=%s %v", media.Title, media.Meta)
e := event.Event{
Id: model.EventPlaylistPreInsert,
Cancelled: false,
Data: model.PlaylistInsertEvent{
Playlist: p.Playlist.Copy(),
Index: index,
Media: media,
},
}
p.eventManager.Call(&e)
if e.Cancelled {
lg.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(
model.EventPlaylistUpdate,
model.PlaylistUpdateEvent{Playlist: p.Playlist.Copy()},
)
p.eventManager.CallA(
model.EventPlaylistInsert,
model.PlaylistInsertEvent{
Playlist: p.Playlist.Copy(),
Index: index,
Media: media,
},
)
}
func (p *corePlaylist) Delete(index int) *model.Media {
lg.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 {
lg.Warnf("media at index %d does not exist", index)
}
p.eventManager.CallA(
model.EventPlaylistUpdate,
model.PlaylistUpdateEvent{Playlist: p.Playlist.Copy()})
return m
}
func (p *corePlaylist) Move(src int, dst int) {
lg.Infof("from media from index %d to %d", src, dst)
if src >= p.Size() || src < 0 {
lg.Warnf("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(
model.EventPlaylistUpdate,
model.PlaylistUpdateEvent{Playlist: p.Playlist.Copy()})
}
func (p *corePlaylist) Next() *model.Media {
lg.Infof("[Playlists] %s get next media with random=%t", p, p.Mode == model.PlaylistModeRandom)
if p.Size() == 0 {
lg.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
}

44
controller/core/plugin.go Normal file
View File

@@ -0,0 +1,44 @@
package core
import (
"AynaLivePlayer/controller"
"github.com/sirupsen/logrus"
)
type PluginController struct {
plugins map[string]controller.Plugin
}
func NewPluginController() controller.IPluginController {
return &PluginController{
plugins: make(map[string]controller.Plugin),
}
}
func (p *PluginController) LoadPlugin(plugin controller.Plugin) {
lg.Info("[Plugin] Loading plugin: " + plugin.Name())
if _, ok := p.plugins[plugin.Name()]; ok {
logrus.Warnf("[Plugin] plugin with same name already exists, skip")
return
}
if err := plugin.Enable(); err != nil {
lg.Warnf("Failed to load plugin: %s, %s", plugin.Name(), err)
return
}
p.plugins[plugin.Name()] = plugin
}
func (p *PluginController) LoadPlugins(plugins ...controller.Plugin) {
for _, plugin := range plugins {
p.LoadPlugin(plugin)
}
}
func (p *PluginController) ClosePlugins() {
for _, plugin := range p.plugins {
if err := plugin.Disable(); err != nil {
lg.Warnf("Failed to close plugin: %s, %s", plugin.Name(), err)
continue
}
}
}

114
controller/core/provider.go Normal file
View File

@@ -0,0 +1,114 @@
package core
import (
"AynaLivePlayer/config"
"AynaLivePlayer/controller"
"AynaLivePlayer/model"
"AynaLivePlayer/provider"
)
type ProviderController struct {
config.BaseConfig
Priority []string
LocalDir string
}
func (pc *ProviderController) Name() string {
return "Provider"
}
func NewProviderController() controller.IProviderController {
p := &ProviderController{
Priority: []string{"netease", "kuwo", "bilibili", "local", "bilibili-video"},
LocalDir: "./music",
}
config.LoadConfig(p)
provider.NewLocal(p.LocalDir)
return p
}
func (pc *ProviderController) GetPriority() []string {
return pc.Priority
}
func (pc *ProviderController) PrepareMedia(media *model.Media) error {
var err error
if media.Title == "" || !media.Cover.Exists() {
lg.Trace("fetching media info")
if err = provider.UpdateMedia(media); err != nil {
lg.Warn("fail to prepare media when fetch info", err)
return err
}
}
if media.Url == "" {
lg.Trace("fetching media url")
if err = provider.UpdateMediaUrl(media); err != nil {
lg.Warn("fail to prepare media when url", err)
return err
}
}
if media.Lyric == "" {
lg.Trace("fetching media lyric")
if err = provider.UpdateMediaLyric(media); err != nil {
lg.Warn("fail to prepare media when lyric", err)
}
}
return nil
}
func (pc *ProviderController) MediaMatch(keyword string) *model.Media {
lg.Infof("Match media for %s", keyword)
for _, p := range pc.Priority {
if pr, ok := provider.Providers[p]; ok {
m := pr.MatchMedia(keyword)
if m == nil {
continue
}
if err := provider.UpdateMedia(m); err == nil {
return m
}
} else {
lg.Warnf("Provider %s not exist", p)
}
}
return nil
}
func (pc *ProviderController) Search(keyword string) ([]*model.Media, error) {
lg.Infof("Search for %s", keyword)
for _, p := range pc.Priority {
if pr, ok := provider.Providers[p]; ok {
r, err := pr.Search(keyword)
if err != nil {
lg.Warn("Provider %s return err", err)
continue
}
return r, err
} else {
lg.Warnf("Provider %s not exist", p)
}
}
return nil, provider.ErrorNoSuchProvider
}
func (pc *ProviderController) SearchWithProvider(keyword string, p string) ([]*model.Media, error) {
lg.Infof("Search for %s using %s", keyword, p)
if pr, ok := provider.Providers[p]; ok {
r, err := pr.Search(keyword)
return r, err
}
lg.Warnf("Provider %s not exist", p)
return nil, provider.ErrorNoSuchProvider
}
func (pc *ProviderController) PreparePlaylist(playlist controller.IPlaylist) error {
lg.Debug("Prepare playlist ", playlist.Name())
medias, err := provider.GetPlaylist(&playlist.Model().Meta)
if err != nil {
lg.Warn("prepare playlist failed ", err)
return err
}
controller.ApplyUser(medias, controller.SystemUser)
playlist.Replace(medias)
return nil
}

View File

@@ -1,23 +0,0 @@
package controller
import (
"AynaLivePlayer/event"
"AynaLivePlayer/liveclient"
)
var DanmuHandlers []DanmuHandler
type DanmuHandler interface {
Execute(anmu *liveclient.DanmuMessage)
}
func AddDanmuHandler(handlers ...DanmuHandler) {
DanmuHandlers = append(DanmuHandlers, handlers...)
}
func danmuHandler(event *event.Event) {
danmu := event.Data.(*liveclient.DanmuMessage)
for _, cmd := range DanmuHandlers {
cmd.Execute(danmu)
}
}

View File

@@ -1,76 +0,0 @@
package controller
import (
"AynaLivePlayer/config"
"AynaLivePlayer/liveclient"
"AynaLivePlayer/player"
"AynaLivePlayer/provider"
"fmt"
)
var MainPlayer *player.Player
var LiveClient liveclient.LiveClient
var CurrentLyric *player.Lyric
var CurrentMedia *player.Media
func Initialize() {
MainPlayer = player.NewPlayer()
SetAudioDevice(config.Player.AudioDevice)
SetVolume(config.Player.Volume)
UserPlaylist = player.NewPlaylist("user", player.PlaylistConfig{RandomNext: config.Player.UserPlaylistRandom})
SystemPlaylist = player.NewPlaylist("system", player.PlaylistConfig{RandomNext: config.Player.PlaylistRandom})
PlaylistManager = make([]*player.Playlist, 0)
History = player.NewPlaylist("history", player.PlaylistConfig{RandomNext: false})
HistoryUser = &player.User{Name: "History"}
loadPlaylists()
config.LoadConfig(LiveRoomManager)
LiveRoomManager.InitializeRooms()
CurrentLyric = player.NewLyric("")
MainPlayer.ObserveProperty("idle-active", handleMpvIdlePlayNext)
UserPlaylist.Handler.RegisterA(player.EventPlaylistInsert, "controller.playnextwhenadd", handlePlaylistAdd)
MainPlayer.ObserveProperty("time-pos", handleLyricUpdate)
MainPlayer.Start()
}
func CloseAndSave() {
// set value to global config
config.Player.PlaylistRandom = SystemPlaylist.Config.RandomNext
config.Player.UserPlaylistRandom = UserPlaylist.Config.RandomNext
_ = config.SaveToConfigFile(config.ConfigPath)
}
func loadPlaylists() {
l.Info("Loading playlists ", config.Player.Playlists)
if len(config.Player.Playlists) != len(config.Player.Playlists) {
l.Warn("playlist id and provider does not have same length")
return
}
for i := 0; i < len(config.Player.Playlists); i++ {
pc := config.Player.Playlists[i]
p := player.NewPlaylist(fmt.Sprintf("%s-%s", pc.Provider, pc.ID), player.PlaylistConfig{})
p.Meta = provider.Meta{
Name: pc.Provider,
Id: pc.ID,
}
PlaylistManager = append(PlaylistManager, p)
}
if config.Player.PlaylistIndex < 0 || config.Player.PlaylistIndex >= len(config.Player.Playlists) {
config.Player.PlaylistIndex = 0
l.Warn("playlist index did not find")
return
}
go func() {
c := config.Player.PlaylistIndex
err := PreparePlaylist(PlaylistManager[c])
if err != nil {
return
}
SetSystemPlaylist(c)
}()
}

View File

@@ -1,35 +0,0 @@
package controller
import (
"AynaLivePlayer/config"
"AynaLivePlayer/event"
"AynaLivePlayer/player"
"github.com/aynakeya/go-mpv"
)
func handleMpvIdlePlayNext(property *mpv.EventProperty) {
isIdle := property.Data.(mpv.Node).Value.(bool)
if isIdle {
l.Info("mpv went idle, try play next")
PlayNext()
}
}
func handlePlaylistAdd(event *event.Event) {
if MainPlayer.IsIdle() {
PlayNext()
return
}
if config.Player.SkipPlaylist && CurrentMedia != nil && CurrentMedia.User == player.PlaylistUser {
PlayNext()
return
}
}
func handleLyricUpdate(property *mpv.EventProperty) {
if property.Data == nil {
return
}
t := property.Data.(mpv.Node).Value.(float64)
CurrentLyric.Update(t)
}

View File

@@ -1,170 +1,30 @@
package controller
import (
"AynaLivePlayer/config"
"AynaLivePlayer/event"
"AynaLivePlayer/common/event"
"AynaLivePlayer/liveclient"
"errors"
"fmt"
"AynaLivePlayer/model"
)
var LiveRoomManager = &LiveRooms{
LiveRoomPath: "liverooms.json",
LiveRooms: []*LiveRoom{
{
ClientName: "bilibili",
ID: "9076804",
AutoConnect: false,
},
{
ClientName: "bilibili",
ID: "3819533",
AutoConnect: false,
},
},
type DanmuCommandExecutor interface {
Match(command string) bool
Execute(command string, args []string, danmu *liveclient.DanmuMessage)
}
type LiveRooms struct {
LiveRoomPath string
LiveRooms []*LiveRoom `ini:"-"`
type ILiveRoomController interface {
Size() int
Get(index int) ILiveRoom
GetRoomStatus(index int) bool
Connect(index int) error
Disconnect(index int) error
AddRoom(clientName, roomId string) (*model.LiveRoom, error)
DeleteRoom(index int) error
AddDanmuCommand(executor DanmuCommandExecutor)
}
func (lr *LiveRooms) Name() string {
return "LiveRoom"
}
func (lr *LiveRooms) Size() int {
return len(lr.LiveRooms)
}
func (lr *LiveRooms) OnLoad() {
_ = config.LoadJson(lr.LiveRoomPath, &lr.LiveRooms)
}
func (lr *LiveRooms) OnSave() {
_ = config.SaveJson(lr.LiveRoomPath, &lr.LiveRooms)
}
func (lr *LiveRooms) InitializeRooms() {
for i := 0; i < len(lr.LiveRooms); i++ {
if lr.LiveRooms[i].client == nil {
lr.LiveRooms[i].Init()
}
}
go func() {
for i := 0; i < len(lr.LiveRooms); i++ {
if lr.LiveRooms[i].AutoConnect {
go lr.LiveRooms[i].Connect()
}
}
}()
}
func (lr *LiveRooms) GetRoom(index int) *LiveRoom {
if index < 0 || index >= len(lr.LiveRooms) {
return nil
}
return lr.LiveRooms[index]
}
func (lr *LiveRooms) AddRoom(clientName, roomId string) (*LiveRoom, error) {
l.Infof("add live client (%s) for %s", clientName, roomId)
rm := &LiveRoom{
ClientName: clientName,
ID: roomId,
AutoConnect: false,
}
err := rm.Init()
l.Infof("live client (%s) %s init failed", clientName, roomId)
if err != nil {
return nil, err
}
lr.LiveRooms = append(lr.LiveRooms, rm)
return rm, nil
}
func (lr *LiveRooms) ConnectRoom(index int) error {
l.Infof("Try to start LiveRoom.index=%d", index)
if index < 0 || index >= len(lr.LiveRooms) {
l.Warnf("LiveRoom.index=%d not found", index)
return errors.New("index out of range")
}
lr.LiveRooms[index].client.Connect()
return nil
}
func (lr *LiveRooms) DisconnectRoom(index int) error {
l.Infof("Try to Disconnect LiveRoom.index=%d", index)
if index < 0 || index >= len(lr.LiveRooms) {
l.Warnf("LiveRoom.index=%d not found", index)
return errors.New("index out of range")
}
lr.LiveRooms[index].client.Disconnect()
return nil
}
func (lr *LiveRooms) DeleteRoom(index int) error {
l.Infof("Try to remove LiveRoom.index=%d", index)
if index < 0 || index >= len(lr.LiveRooms) {
l.Warnf("LiveRoom.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.Handler().UnregisterAll()
_ = lr.LiveRooms[index].Disconnect()
lr.LiveRooms = append(lr.LiveRooms[:index], lr.LiveRooms[index+1:]...)
return nil
}
type LiveRoom struct {
ClientName string
ID string
AutoConnect bool
client liveclient.LiveClient
}
func (r *LiveRoom) Init() (err error) {
if r.client != nil {
return nil
}
r.client, err = liveclient.NewLiveClient(r.ClientName, r.ID)
if err != nil {
return
}
r.client.Handler().Register(&event.EventHandler{
EventId: liveclient.EventMessageReceive,
Name: "controller.commandexecutor",
Handler: danmuCommandHandler,
})
r.client.Handler().RegisterA(
liveclient.EventMessageReceive,
"controller.danmu.handler",
danmuHandler)
return nil
}
func (r *LiveRoom) Connect() error {
if r.client == nil {
return errors.New("client hasn't initialized yet")
}
r.client.Connect()
return nil
}
func (r *LiveRoom) Disconnect() error {
if r.client == nil {
return errors.New("client hasn't initialized yet")
}
r.client.Disconnect()
return nil
}
func (r *LiveRoom) Title() string {
return fmt.Sprintf("%s-%s", r.ClientName, r.ID)
}
func (r *LiveRoom) Client() liveclient.LiveClient {
return r.client
type ILiveRoom interface {
Model() *model.LiveRoom // should return mutable model (not a copy)
Title() string // should be same as Model().Title
Status() bool
EventManager() *event.Manager
}

34
controller/playcontrol.go Normal file
View File

@@ -0,0 +1,34 @@
package controller
import (
"AynaLivePlayer/common/event"
"AynaLivePlayer/model"
"AynaLivePlayer/player"
)
type IPlayController interface {
EventManager() *event.Manager
GetPlaying() *model.Media
GetPlayer() player.IPlayer
PlayNext()
Play(media *model.Media)
Add(keyword string, user interface{})
AddWithProvider(keyword string, provider string, user interface{})
Seek(position float64, absolute bool)
Toggle() bool
SetVolume(volume float64)
Destroy()
GetCurrentAudioDevice() string
GetAudioDevices() []model.AudioDevice
SetAudioDevice(device string)
GetLyric() ILyricLoader
GetSkipPlaylist() bool
SetSkipPlaylist(b bool)
}
type ILyricLoader interface {
EventManager() *event.Manager
Get() *model.Lyric
Reload(lyric string)
Update(time float64)
}

View File

@@ -1,130 +0,0 @@
package controller
import (
"AynaLivePlayer/config"
"AynaLivePlayer/player"
"AynaLivePlayer/provider"
)
func PlayNext() {
l.Info("try to play next possible media")
if UserPlaylist.Size() == 0 && SystemPlaylist.Size() == 0 {
return
}
var media *player.Media
if UserPlaylist.Size() != 0 {
media = UserPlaylist.Pop()
} else if SystemPlaylist.Size() != 0 {
media = SystemPlaylist.Next()
}
Play(media)
}
func Play(media *player.Media) {
l.Infof("prepare media %s", media.Title)
err := PrepareMedia(media)
if err != nil {
l.Warn("prepare media failed. try play next")
PlayNext()
return
}
CurrentMedia = media
AddToHistory(media)
if err := MainPlayer.Play(media); err != nil {
l.Warn("play failed", err)
return
}
CurrentLyric.Reload(media.Lyric)
// reset
media.Url = ""
}
func Add(keyword string, user interface{}) {
media := MediaMatch(keyword)
if media == nil {
medias, err := Search(keyword)
if err != nil {
l.Warnf("search for %s, got error %s", keyword, err)
return
}
if len(medias) == 0 {
l.Info("search for %s, got no result", keyword)
return
}
media = medias[0]
}
media.User = user
l.Infof("add media %s (%s)", media.Title, media.Artist)
UserPlaylist.Insert(-1, media)
}
func AddWithProvider(keyword string, pname string, user interface{}) {
media := provider.MatchMedia(pname, keyword)
if media == nil {
medias, err := provider.Search(pname, keyword)
if err != nil {
l.Warnf("search for %s, got error %s", keyword, err)
return
}
if len(medias) == 0 {
l.Infof("search for %s, got no result", keyword)
return
}
media = medias[0]
}
media.User = user
l.Infof("add media %s (%s)", media.Title, media.Artist)
UserPlaylist.Insert(-1, media)
}
func Seek(position float64, absolute bool) {
if err := MainPlayer.Seek(position, absolute); err != nil {
l.Warnf("seek to position %f (%t) failed, %s", position, absolute, err)
}
}
func Toggle() (b bool) {
var err error
if MainPlayer.IsPaused() {
err = MainPlayer.Unpause()
b = false
} else {
err = MainPlayer.Pause()
b = true
}
if err != nil {
l.Warn("toggle failed", err)
}
return
}
func SetVolume(volume float64) {
if MainPlayer.SetVolume(volume) != nil {
l.Warnf("set mpv volume to %f failed", volume)
return
}
config.Player.Volume = volume
}
func Destroy() {
MainPlayer.Stop()
}
func GetAudioDevices() []player.AudioDevice {
dl, err := MainPlayer.GetAudioDeviceList()
if err != nil {
return make([]player.AudioDevice, 0)
}
return dl
}
func SetAudioDevice(device string) {
l.Infof("set audio device to %s", device)
if err := MainPlayer.SetAudioDevice(device); err != nil {
l.Warnf("set mpv audio device to %s failed, %s", device, err)
MainPlayer.SetAudioDevice("auto")
config.Player.AudioDevice = "auto"
return
}
config.Player.AudioDevice = device
}

View File

@@ -1,104 +1,34 @@
package controller
import (
"AynaLivePlayer/config"
"AynaLivePlayer/player"
"AynaLivePlayer/provider"
"fmt"
"AynaLivePlayer/common/event"
"AynaLivePlayer/model"
)
var UserPlaylist *player.Playlist
var History *player.Playlist
var HistoryUser *player.User
var SystemPlaylist *player.Playlist
var PlaylistManager []*player.Playlist
func AddToHistory(media *player.Media) {
l.Tracef("add media %s (%s) to history", media.Title, media.Artist)
media = media.Copy()
// reset url for future use
media.Url = ""
if History.Size() >= 1024 {
History.Replace([]*player.Media{})
}
History.Push(media)
return
type IPlaylistController interface {
Size() int
GetHistory() IPlaylist
AddToHistory(media *model.Media)
GetDefault() IPlaylist
GetCurrent() IPlaylist
Get(index int) IPlaylist
Add(pname string, uri string) IPlaylist
Remove(index int) IPlaylist
SetDefault(index int) error
PreparePlaylistByIndex(index int) error
}
func ToHistoryMedia(media *player.Media) *player.Media {
media = media.Copy()
media.User = HistoryUser
return media
}
func ToSystemMedia(media *player.Media) *player.Media {
media = media.Copy()
media.User = player.SystemUser
return media
}
func AddPlaylist(pname string, uri string) *player.Playlist {
l.Infof("try add playlist %s with provider %s", uri, pname)
id, err := provider.FormatPlaylistUrl(pname, uri)
if err != nil || id == "" {
l.Warnf("fail to format %s playlist id for %s", uri, pname)
return nil
}
p := player.NewPlaylist(fmt.Sprintf("%s-%s", pname, id), player.PlaylistConfig{})
p.Meta = provider.Meta{
Name: pname,
Id: id,
}
PlaylistManager = append(PlaylistManager, p)
config.Player.Playlists = append(config.Player.Playlists, &config.PlayerPlaylist{
ID: uri,
Provider: pname,
})
return p
}
func RemovePlaylist(index int) {
l.Infof("Try to remove playlist.index=%d", index)
if index < 0 || index >= len(PlaylistManager) {
l.Warnf("playlist.index=%d not found", index)
return
}
if index == config.Player.PlaylistIndex {
l.Info("Delete current system playlist, reset system playlist to index = 0")
SetSystemPlaylist(0)
}
if index < config.Player.PlaylistIndex {
l.Debugf("Delete playlist before system playlist (index=%d), reduce system playlist index by 1", config.Player.PlaylistIndex)
config.Player.PlaylistIndex = config.Player.PlaylistIndex - 1
}
PlaylistManager = append(PlaylistManager[:index], PlaylistManager[index+1:]...)
config.Player.Playlists = append(config.Player.Playlists[:index], config.Player.Playlists[index+1:]...)
}
func SetSystemPlaylist(index int) {
l.Infof("try set system playlist to playlist.id=%d", index)
if index < 0 || index >= len(PlaylistManager) {
l.Warn("playlist.index=%d not found", index)
return
}
err := PreparePlaylist(PlaylistManager[index])
if err != nil {
return
}
medias := PlaylistManager[index].Playlist
config.Player.PlaylistIndex = index
ApplyUser(medias, player.PlaylistUser)
SystemPlaylist.Replace(medias)
}
func PreparePlaylistByIndex(index int) {
l.Infof("try prepare playlist.id=%d", index)
if index < 0 || index >= len(PlaylistManager) {
l.Warn("playlist.id=%d not found", index)
return
}
err := PreparePlaylist(PlaylistManager[index])
if err != nil {
return
}
type IPlaylist interface {
Model() *model.Playlist // mutable model (not a copy)
EventManager() *event.Manager
Name() string
Size() int
Get(index int) *model.Media
Pop() *model.Media
Replace(medias []*model.Media)
Push(media *model.Media)
Insert(index int, media *model.Media)
Delete(index int) *model.Media
Move(src int, dst int)
Next() *model.Media
}

View File

@@ -6,25 +6,8 @@ type Plugin interface {
Disable() error
}
func LoadPlugin(plugin Plugin) {
l.Info("Loading plugin: " + plugin.Name())
if err := plugin.Enable(); err != nil {
l.Warnf("Failed to load plugin: %s, %s", plugin.Name(), err)
}
}
func LoadPlugins(plugins ...Plugin) {
for _, plugin := range plugins {
LoadPlugin(plugin)
}
}
func ClosePlugins(plugins ...Plugin) {
for _, plugin := range plugins {
err := plugin.Disable()
if err != nil {
l.Warnf("Failed to close plugin: %s, %s", plugin.Name(), err)
return
}
}
type IPluginController interface {
LoadPlugin(plugin Plugin)
LoadPlugins(plugins ...Plugin)
ClosePlugins()
}

View File

@@ -1,95 +1,30 @@
package controller
import (
"AynaLivePlayer/config"
"AynaLivePlayer/player"
"AynaLivePlayer/provider"
"AynaLivePlayer/model"
)
func PrepareMedia(media *player.Media) error {
var err error
if media.Title == "" || !media.Cover.Exists() {
l.Trace("fetching media info")
if err = provider.UpdateMedia(media); err != nil {
l.Warn("fail to prepare media when fetch info", err)
return err
}
}
if media.Url == "" {
l.Trace("fetching media url")
if err = provider.UpdateMediaUrl(media); err != nil {
l.Warn("fail to prepare media when url", err)
return err
}
}
if media.Lyric == "" {
l.Trace("fetching media lyric")
if err = provider.UpdateMediaLyric(media); err != nil {
l.Warn("fail to prepare media when lyric", err)
}
}
return nil
var PlaylistUser = &model.User{Name: "Playlists"}
var SystemUser = &model.User{Name: "System"}
var HistoryUser = &model.User{Name: "History"}
type IProviderController interface {
GetPriority() []string
PrepareMedia(media *model.Media) error
MediaMatch(keyword string) *model.Media
Search(keyword string) ([]*model.Media, error)
SearchWithProvider(keyword string, provider string) ([]*model.Media, error)
PreparePlaylist(playlist IPlaylist) error
}
func MediaMatch(keyword string) *player.Media {
l.Infof("Match media for %s", keyword)
for _, p := range config.Provider.Priority {
if pr, ok := provider.Providers[p]; ok {
m := pr.MatchMedia(keyword)
if m == nil {
continue
}
if err := provider.UpdateMedia(m); err == nil {
return m
}
} else {
l.Warnf("Provider %s not exist", p)
}
}
return nil
}
func Search(keyword string) ([]*player.Media, error) {
l.Infof("Search for %s", keyword)
for _, p := range config.Provider.Priority {
if pr, ok := provider.Providers[p]; ok {
r, err := pr.Search(keyword)
if err != nil {
l.Warn("Provider %s return err", err)
continue
}
return r, err
} else {
l.Warnf("Provider %s not exist", p)
}
}
return nil, provider.ErrorNoSuchProvider
}
func SearchWithProvider(keyword string, p string) ([]*player.Media, error) {
l.Infof("Search for %s using %s", keyword, p)
if pr, ok := provider.Providers[p]; ok {
r, err := pr.Search(keyword)
return r, err
}
l.Warnf("Provider %s not exist", p)
return nil, provider.ErrorNoSuchProvider
}
func ApplyUser(medias []*player.Media, user interface{}) {
func ApplyUser(medias []*model.Media, user interface{}) {
for _, m := range medias {
m.User = user
}
}
func PreparePlaylist(playlist *player.Playlist) error {
l.Debug("Prepare playlist ", playlist.Meta.(provider.Meta))
medias, err := provider.GetPlaylist(playlist.Meta.(provider.Meta))
if err != nil {
l.Warn("prepare playlist failed ", err)
return err
}
ApplyUser(medias, player.SystemUser)
playlist.Replace(medias)
return nil
func ToSpMedia(media *model.Media, user *model.User) *model.Media {
media = media.Copy()
media.User = user
return media
}