Initial commit

This commit is contained in:
Aynakeya
2022-06-21 13:02:22 -07:00
commit 9f75839ebc
161 changed files with 18766 additions and 0 deletions

38
player/event.go Normal file
View File

@@ -0,0 +1,38 @@
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
}
type PlayEvent struct {
Media *Media
}
type LyricUpdateEvent struct {
Lyrics *Lyric
Time float64
Lyric *LyricLine
}
type LyricReloadEvent struct {
Lyrics *Lyric
}

89
player/lyric.go Normal file
View File

@@ -0,0 +1,89 @@
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
}
type Lyric struct {
Lyrics []LyricLine
Handler *event.Handler
}
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]
}
lrcs = append(lrcs, LyricLine{
Time: 99999999999,
Lyric: "",
})
l.Lyrics = lrcs
l.Handler.CallA(EventLyricReload, LyricReloadEvent{Lyrics: l})
return
}
func (l *Lyric) Update(time float64) {
l.Handler.CallA(EventLyricUpdate, LyricUpdateEvent{
Lyrics: l,
Time: time,
Lyric: l.Find(time),
})
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) []LyricLine {
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
}
return l.Lyrics[i+prev : i+1+next]
}
}
return nil
}
func NewLyric(lyric string) *Lyric {
l := &Lyric{Handler: event.NewHandler()}
l.Reload(lyric)
return l
}

23
player/lyric_test.go Normal file
View File

@@ -0,0 +1,23 @@
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)
}
}

36
player/media.go Normal file
View File

@@ -0,0 +1,36 @@
package player
import "AynaLivePlayer/liveclient"
type Media struct {
Title string
Artist string
Cover string
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
}

22
player/media_test.go Normal file
View File

@@ -0,0 +1,22 @@
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)
}

BIN
player/mpv-2.dll Normal file

Binary file not shown.

156
player/player.go Normal file
View File

@@ -0,0 +1,156 @@
package player
import (
"AynaLivePlayer/event"
"AynaLivePlayer/logger"
"AynaLivePlayer/util"
"github.com/aynakeya/go-mpv"
"github.com/sirupsen/logrus"
)
const MODULE_PLAYER = "Player.Player"
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)
p.l().Trace("set user-agent for mpv player")
if val, ok := media.Header["user-agent"]; ok {
err := p.libmpv.SetPropertyString("user-agent", val)
if err != nil {
p.l().Warn("set player user-agent failed", err)
return err
}
}
p.l().Trace("set referrer for mpv player")
if val, ok := media.Header["referrer"]; ok {
err := p.libmpv.SetPropertyString("referrer", val)
if err != nil {
p.l().Warn("set player referrer failed", err)
return err
}
}
p.l().Debug("mpv command load file", media)
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
}

25
player/player_test.go Normal file
View File

@@ -0,0 +1,25 @@
package player
import (
"fmt"
"github.com/aynakeya/go-mpv"
"testing"
"time"
)
func TestPlayer(t *testing.T) {
player := NewPlayer()
player.Start()
defer player.Stop()
player.ObserveProperty("time-pos", func(property *mpv.EventProperty) {
fmt.Println(1, property.Data)
})
player.ObserveProperty("percent-pos", func(property *mpv.EventProperty) {
fmt.Println(2, property.Data)
})
player.Play(&Media{
Url: "https://ia600809.us.archive.org/19/items/VillagePeopleYMCAOFFICIALMusicVideo1978/Village%20People%20-%20YMCA%20OFFICIAL%20Music%20Video%201978.mp4",
})
time.Sleep(time.Second * 15)
}

144
player/playlist.go Normal file
View File

@@ -0,0 +1,144 @@
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()
media := p.Playlist[0]
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)
defer p.Handler.CallA(EventPlaylistUpdate, PlaylistUpdateEvent{Playlist: p})
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().Debug("media=", *media)
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) 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]
}

22
player/playlist_test.go Normal file
View File

@@ -0,0 +1,22 @@
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, " ")
}
}

8
player/user.go Normal file
View File

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