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,44 +0,0 @@
package player
import (
"AynaLivePlayer/event"
)
const (
EventPlay event.EventId = "player.play"
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"
)
type PlaylistInsertEvent struct {
Playlist *Playlist
Index int
Media *Media
}
type PlaylistUpdateEvent struct {
Playlist *Playlist
}
func newPlaylistUpdateEvent(playlist *Playlist) PlaylistUpdateEvent {
return PlaylistUpdateEvent{
Playlist: playlist,
}
}
type PlayEvent struct {
Media *Media
}
type LyricUpdateEvent struct {
Lyrics *Lyric
Time float64
Lyric *LyricLine
}
type LyricReloadEvent struct {
Lyrics *Lyric
}

View File

@@ -1,113 +0,0 @@
package player
import (
"AynaLivePlayer/event"
"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 {
Current *LyricLine
Prev []*LyricLine
Next []*LyricLine
}
type Lyric struct {
Lyrics []*LyricLine
Handler *event.Handler
prev float64
}
func (l *Lyric) Reload(lyric string) {
tmp := make(map[float64]*LyricLine)
times := make([]float64, 0)
for _, line := range strings.Split(lyric, "\n") {
lrc := timeTagRegex.ReplaceAllString(line, "")
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: "",
})
l.Lyrics = lrcs
l.Handler.CallA(EventLyricReload, LyricReloadEvent{Lyrics: l})
return
}
func (l *Lyric) Update(time float64) {
lrc := l.Find(time)
if lrc == nil {
return
}
if l.prev == lrc.Time {
return
}
l.prev = lrc.Time
l.Handler.CallA(EventLyricUpdate, LyricUpdateEvent{
Lyrics: l,
Time: time,
Lyric: lrc,
})
return
}
func (l *Lyric) Find(time float64) *LyricLine {
for i := 0; i < len(l.Lyrics)-1; i++ {
if l.Lyrics[i].Time <= time && time < l.Lyrics[i+1].Time {
return l.Lyrics[i]
}
}
return nil
}
func (l *Lyric) FindContext(time float64, prev int, next int) *LyricContext {
for i := 0; i < len(l.Lyrics)-1; i++ {
if l.Lyrics[i].Time <= time && time < l.Lyrics[i+1].Time {
if (i + prev) < 0 {
prev = -i
}
if (i + 1 + next) > len(l.Lyrics) {
next = len(l.Lyrics) - i - 1
}
//l.Lyrics[i+prev : i+1+next]
return &LyricContext{
Current: l.Lyrics[i],
Prev: l.Lyrics[i+prev : i],
Next: l.Lyrics[i+1 : i+1+next],
}
}
}
return nil
}
func NewLyric(lyric string) *Lyric {
l := &Lyric{Handler: event.NewHandler(), prev: -1}
l.Reload(lyric)
return l
}

View File

@@ -1,23 +0,0 @@
package player
import (
"fmt"
"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 := NewLyric(testLyric)
for _, lrc := range lryic.Lyrics {
fmt.Println(lrc)
}
}
func TestLyricFind(t *testing.T) {
lryic := NewLyric(testLyric)
fmt.Println(lryic.Find(90.4))
for _, l := range lryic.FindContext(90.4, -2, 2) {
fmt.Println(l)
}
}

View File

@@ -1,54 +0,0 @@
package player
import (
"AynaLivePlayer/liveclient"
"github.com/jinzhu/copier"
)
type Picture struct {
Url string
Data []byte
}
func (p Picture) Exists() bool {
return p.Url != "" || p.Data != nil
}
type Media struct {
Title string
Artist string
Cover Picture
Album string
Lyric string
Url string
Header map[string]string
User interface{}
Meta interface{}
}
func (m *Media) ToUser() *User {
if u, ok := m.User.(*User); ok {
return u
}
return &User{Name: m.DanmuUser().Username}
}
func (m *Media) SystemUser() *User {
if u, ok := m.User.(*User); ok {
return u
}
return nil
}
func (m *Media) DanmuUser() *liveclient.DanmuUser {
if u, ok := m.User.(*liveclient.DanmuUser); ok {
return u
}
return nil
}
func (m *Media) Copy() *Media {
newMedia := &Media{}
copier.Copy(newMedia, m)
return newMedia
}

View File

@@ -1,30 +0,0 @@
package player
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)
}

View File

@@ -1,187 +1,24 @@
package player
import (
"AynaLivePlayer/event"
"AynaLivePlayer/logger"
"AynaLivePlayer/util"
"github.com/aynakeya/go-mpv"
"github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
"AynaLivePlayer/common/event"
"AynaLivePlayer/common/logger"
"AynaLivePlayer/model"
)
const MODULE_PLAYER = "Player.Player"
var lg = logger.Logger.WithField("Module", "PlayControl")
type PropertyHandlerFunc func(property *mpv.EventProperty)
type Player struct {
running bool
libmpv *mpv.Mpv
Playing *Media
PropertyHandler map[string][]PropertyHandlerFunc
EventHandler *event.Handler
}
func NewPlayer() *Player {
player := &Player{
running: true,
libmpv: mpv.Create(),
PropertyHandler: make(map[string][]PropertyHandlerFunc),
EventHandler: event.NewHandler(),
}
err := player.libmpv.Initialize()
if err != nil {
player.l().Error("initialize libmpv failed")
return nil
}
player.libmpv.SetOptionString("vo", "null")
player.l().Info("initialize libmpv success")
return player
}
func (p *Player) Start() {
p.l().Info("starting mpv player")
go func() {
for p.running {
e := p.libmpv.WaitEvent(1)
if e == nil {
p.l().Warn("event loop got nil event")
}
p.l().Trace("new event", e)
if e.EventId == mpv.EVENT_PROPERTY_CHANGE {
property := e.Property()
p.l().Trace("receive property change event", property)
for _, handler := range p.PropertyHandler[property.Name] {
// todo: @3
go handler(&property)
}
}
if e.EventId == mpv.EVENT_SHUTDOWN {
p.l().Info("libmpv shutdown")
p.Stop()
}
}
}()
}
func (p *Player) Stop() {
p.l().Info("stopping mpv player")
p.running = false
p.libmpv.TerminateDestroy()
}
func (p *Player) l() *logrus.Entry {
return logger.Logger.WithField("Module", MODULE_PLAYER)
}
func (p *Player) Play(media *Media) error {
p.l().Infof("Play media %s", media.Url)
if val, ok := media.Header["User-Agent"]; ok {
p.l().Debug("set user-agent for mpv player")
err := p.libmpv.SetPropertyString("user-agent", val)
if err != nil {
p.l().Warn("set player user-agent failed", err)
return err
}
}
if val, ok := media.Header["Referer"]; ok {
p.l().Debug("set referrer for mpv player")
err := p.libmpv.SetPropertyString("referrer", val)
if err != nil {
p.l().Warn("set player referrer failed", err)
return err
}
}
p.l().Debugf("mpv command load file %s %s", media.Title, media.Url)
if err := p.libmpv.Command([]string{"loadfile", media.Url}); err != nil {
p.l().Warn("mpv load media failed", media)
return err
}
p.Playing = media
p.EventHandler.CallA(EventPlay, PlayEvent{Media: media})
return nil
}
func (p *Player) IsPaused() bool {
property, err := p.libmpv.GetProperty("pause", mpv.FORMAT_FLAG)
if err != nil {
p.l().Warn("get property pause failed", err)
return false
}
return property.(bool)
}
func (p *Player) Pause() error {
p.l().Tracef("pause")
return p.libmpv.SetProperty("pause", mpv.FORMAT_FLAG, true)
}
func (p *Player) Unpause() error {
p.l().Tracef("unpause")
return p.libmpv.SetProperty("pause", mpv.FORMAT_FLAG, false)
}
// SetVolume set mpv volume, from 0.0 - 100.0
func (p *Player) SetVolume(volume float64) error {
p.l().Tracef("set volume to %f", volume)
return p.libmpv.SetProperty("volume", mpv.FORMAT_DOUBLE, volume)
}
func (p *Player) IsIdle() bool {
property, err := p.libmpv.GetProperty("idle-active", mpv.FORMAT_FLAG)
if err != nil {
p.l().Warn("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 *Player) Seek(position float64, absolute bool) error {
p.l().Tracef("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 *Player) ObserveProperty(property string, handler ...PropertyHandlerFunc) error {
p.l().Trace("add property observer for mpv")
p.PropertyHandler[property] = append(p.PropertyHandler[property], handler...)
if len(p.PropertyHandler[property]) == 1 {
return p.libmpv.ObserveProperty(util.Hash64(property), property, mpv.FORMAT_NODE)
}
return nil
}
type AudioDevice struct {
Name string
Description string
}
// GetAudioDeviceList get output device for mpv
// return format is []AudioDevice
func (p *Player) GetAudioDeviceList() ([]AudioDevice, error) {
p.l().Trace("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([]AudioDevice, 0)
gjson.Parse(property.(string)).ForEach(func(key, value gjson.Result) bool {
dl = append(dl, AudioDevice{
Name: value.Get("name").String(),
Description: value.Get("description").String(),
})
return true
})
return dl, nil
}
func (p *Player) SetAudioDevice(device string) error {
p.l().Tracef("set audio device %s for mpv", device)
return p.libmpv.SetPropertyString("audio-device", device)
type IPlayer interface {
Start()
Stop()
Play(media *model.Media) error
IsPaused() bool
Pause() error
Unpause() error
SetVolume(volume float64) error
IsIdle() bool
Seek(position float64, absolute bool) error
ObserveProperty(property model.PlayerProperty, name string, handler event.HandlerFunc) error
GetAudioDeviceList() ([]model.AudioDevice, error)
SetAudioDevice(device string) error
}

204
player/player_mpv.go Normal file
View File

@@ -0,0 +1,204 @@
package player
import (
"AynaLivePlayer/common/event"
"AynaLivePlayer/common/util"
"AynaLivePlayer/model"
"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
}
func NewMpvPlayer() IPlayer {
player := &MpvPlayer{
running: true,
libmpv: mpv.Create(),
propertyWatchedFlag: make(map[model.PlayerProperty]int),
eventManager: event.MainManager.NewChildManager(),
}
err := player.libmpv.Initialize()
if err != nil {
lg.Error("[MPV PlayControl] initialize libmpv failed")
return nil
}
_ = player.libmpv.SetOptionString("vo", "null")
lg.Info("[MPV PlayControl] initialize libmpv success")
player.Start()
return player
}
func (p *MpvPlayer) Start() {
lg.Info("[MPV PlayControl] starting mpv player")
go func() {
for p.running {
e := p.libmpv.WaitEvent(1)
if e == nil {
lg.Warn("[MPV PlayControl] event loop got nil event")
}
lg.Trace("[MPV PlayControl] 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(
model.EventPlayerPropertyUpdate(property),
model.PlayerPropertyUpdateEvent{
Property: property,
Value: value,
})
}
if e.EventId == mpv.EVENT_SHUTDOWN {
lg.Info("[MPV PlayControl] libmpv shutdown")
p.Stop()
}
}
}()
}
func (p *MpvPlayer) Stop() {
lg.Info("[MPV PlayControl] stopping mpv player")
p.running = false
p.libmpv.TerminateDestroy()
}
func (p *MpvPlayer) Play(media *model.Media) error {
lg.Infof("[MPV PlayControl] Play media %s", media.Url)
if val, ok := media.Header["User-Agent"]; ok {
lg.Debug("[MPV PlayControl] set user-agent for mpv player")
err := p.libmpv.SetPropertyString("user-agent", val)
if err != nil {
lg.Warn("[MPV PlayControl] set player user-agent failed", err)
return err
}
}
if val, ok := media.Header["Referer"]; ok {
lg.Debug("[MPV PlayControl] set referrer for mpv player")
err := p.libmpv.SetPropertyString("referrer", val)
if err != nil {
lg.Warn("[MPV PlayControl] set player referrer failed", err)
return err
}
}
lg.Debugf("mpv command load file %s %s", media.Title, media.Url)
if err := p.libmpv.Command([]string{"loadfile", media.Url}); err != nil {
lg.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 {
lg.Warn("[MPV PlayControl] get property pause failed", err)
return false
}
return property.(bool)
}
func (p *MpvPlayer) Pause() error {
lg.Tracef("[MPV PlayControl] pause")
return p.libmpv.SetProperty("pause", mpv.FORMAT_FLAG, true)
}
func (p *MpvPlayer) Unpause() error {
lg.Tracef("[MPV PlayControl] 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 {
lg.Tracef("[MPV PlayControl] 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 {
lg.Warn("[MPV PlayControl] 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 {
lg.Tracef("[MPV PlayControl] 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 {
lg.Trace("[MPV PlayControl] add property observer for mpv")
p.eventManager.RegisterA(
model.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) {
lg.Trace("[MPV PlayControl] 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 {
lg.Tracef("[MPV PlayControl] set audio device %s for mpv", device)
return p.libmpv.SetPropertyString("audio-device", device)
}

View File

@@ -1,6 +1,7 @@
package player
import (
"AynaLivePlayer/model"
"fmt"
"github.com/aynakeya/go-mpv"
"testing"
@@ -18,7 +19,7 @@ func TestPlayer(t *testing.T) {
player.ObserveProperty("percent-pos", func(property *mpv.EventProperty) {
fmt.Println(2, property.Data)
})
player.Play(&Media{
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)

View File

@@ -1,196 +0,0 @@
package player
import (
"AynaLivePlayer/event"
"AynaLivePlayer/logger"
"github.com/sirupsen/logrus"
"math/rand"
"sync"
"time"
)
const MODULE_PLAYLIST = "Player.Playlist"
func init() {
rand.Seed(time.Now().UnixNano())
}
type PlaylistConfig struct {
RandomNext bool
}
type Playlist struct {
Index int
Name string
Config PlaylistConfig
Playlist []*Media
Handler *event.Handler
Meta interface{}
Lock sync.RWMutex
}
func NewPlaylist(name string, config PlaylistConfig) *Playlist {
return &Playlist{
Index: 0,
Name: name,
Config: config,
Playlist: make([]*Media, 0),
Handler: event.NewHandler(),
}
}
func (p *Playlist) l() *logrus.Entry {
return logger.Logger.WithFields(logrus.Fields{
"Module": MODULE_PLAYLIST,
"Name": p.Name,
})
}
func (p *Playlist) Size() int {
p.l().Tracef("getting size=%d", len(p.Playlist))
return len(p.Playlist)
}
func (p *Playlist) Pop() *Media {
p.l().Infof("pop first media")
if p.Size() == 0 {
p.l().Warn("pop first media failed, no media left in the playlist")
return nil
}
p.Lock.Lock()
index := 0
if p.Config.RandomNext {
index = rand.Intn(p.Size())
}
media := p.Playlist[index]
for i := index; i > 0; i-- {
p.Playlist[i] = p.Playlist[i-1]
}
p.Playlist = p.Playlist[1:]
p.Lock.Unlock()
defer p.Handler.CallA(EventPlaylistUpdate, PlaylistUpdateEvent{Playlist: p})
return media
}
func (p *Playlist) Replace(medias []*Media) {
p.Lock.Lock()
p.Playlist = medias
p.Index = 0
p.Lock.Unlock()
p.Handler.CallA(EventPlaylistUpdate, PlaylistUpdateEvent{Playlist: p})
return
}
func (p *Playlist) Push(media *Media) {
p.Insert(-1, media)
return
}
// Insert runtime in O(n) but i don't care
func (p *Playlist) Insert(index int, media *Media) {
p.l().Infof("insert new meida to index %d", index)
p.l().Debugf("media= %s", media.Title)
e := event.Event{
Id: EventPlaylistPreInsert,
Cancelled: false,
Data: PlaylistInsertEvent{
Playlist: p,
Index: index,
Media: media,
},
}
p.Handler.Call(&e)
if e.Cancelled {
p.l().Info("insert new media 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.Playlist = append(p.Playlist, nil)
for i := p.Size() - 1; i > index; i-- {
p.Playlist[i] = p.Playlist[i-1]
}
p.Playlist[index] = media
p.Lock.Unlock()
defer func() {
p.Handler.Call(&event.Event{
Id: EventPlaylistInsert,
Cancelled: false,
Data: PlaylistInsertEvent{
Playlist: p,
Index: index,
Media: media,
},
})
p.Handler.CallA(EventPlaylistUpdate, PlaylistUpdateEvent{Playlist: p})
}()
}
func (p *Playlist) Delete(index int) {
p.l().Infof("from media at index %d", index)
p.Lock.Lock()
if index >= p.Size() || index < 0 {
p.l().Warnf("media at index %d does not exist", index)
p.Lock.Unlock()
return
}
// todo: @5 delete optimization
p.Playlist = append(p.Playlist[:index], p.Playlist[index+1:]...)
p.Lock.Unlock()
defer p.Handler.CallA(EventPlaylistUpdate, PlaylistUpdateEvent{Playlist: p})
}
func (p *Playlist) Move(src int, dest int) {
p.l().Infof("from media from index %d to %d", src, dest)
p.Lock.Lock()
if src >= p.Size() || src < 0 {
p.l().Warnf("media at index %d does not exist", src)
p.Lock.Unlock()
return
}
if dest >= p.Size() {
dest = p.Size() - 1
}
if dest < 0 {
dest = 0
}
if dest == src {
p.l().Warn("src and dest are same, operation not perform")
p.Lock.Unlock()
return
}
step := 1
if dest < src {
step = -1
}
tmp := p.Playlist[src]
for i := src; i != dest; i += step {
p.Playlist[i] = p.Playlist[i+step]
}
p.Playlist[dest] = tmp
p.Lock.Unlock()
defer p.Handler.CallA(EventPlaylistUpdate, PlaylistUpdateEvent{Playlist: p})
}
func (p *Playlist) Next() *Media {
p.l().Infof("get next media with random=%t", p.Config.RandomNext)
if p.Size() == 0 {
p.l().Info("get next media failed, no media left in the playlist")
return nil
}
var index int
index = p.Index
if p.Config.RandomNext {
p.Index = rand.Intn(p.Size())
} else {
p.Index = (p.Index + 1) % p.Size()
}
p.l().Tracef("return index %d, new index %d", index, p.Index)
defer p.Handler.CallA(EventPlaylistUpdate, PlaylistUpdateEvent{Playlist: p})
return p.Playlist[index]
}

View File

@@ -1,22 +0,0 @@
package player
import (
"fmt"
"strconv"
"testing"
)
func TestPlaylist_Insert(t *testing.T) {
pl := NewPlaylist("asdf", PlaylistConfig{RandomNext: false})
for i := 0; i < 10; i++ {
pl.Insert(-1, &Media{Url: strconv.Itoa(i)})
}
pl.Insert(3, &Media{Url: "a"})
pl.Insert(0, &Media{Url: "b"})
pl.Insert(-2, &Media{Url: "x"})
pl.Insert(-1, &Media{Url: "h"})
for i := 0; i < pl.Size(); i++ {
fmt.Print(pl.Playlist[i].Url, " ")
}
}

View File

@@ -1,8 +0,0 @@
package player
type User struct {
Name string
}
var PlaylistUser = &User{Name: "Playlist"}
var SystemUser = &User{Name: "System"}