package player import ( "AynaLivePlayer/event" "AynaLivePlayer/logger" "AynaLivePlayer/util" "github.com/aynakeya/go-mpv" "github.com/sirupsen/logrus" "github.com/tidwall/gjson" ) 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) 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) }