1package main23import (4 "github.com/nmeum/mpvfs/mpv"56 "fmt"7 "sync"8)910type playerState struct {11 mpv *mpv.Client1213 volume uint14 volCond *sync.Cond1516 playlist []string17 playCond *sync.Cond1819 stateCond *sync.Cond20 playing bool2122 posCond *sync.Cond23 pos int2425 errChan chan error26}2728func newPlayerState(mpv *mpv.Client) (*playerState, error) {29 state := &playerState{30 pos: -1,31 mpv: mpv,32 volCond: sync.NewCond(new(sync.Mutex)),33 posCond: sync.NewCond(new(sync.Mutex)),34 playCond: sync.NewCond(new(sync.Mutex)),35 stateCond: sync.NewCond(new(sync.Mutex)),36 errChan: make(chan error, 1),37 }3839 observers := map[string]func(ch <-chan interface{}){40 "pause": state.updateState,41 "volume": state.updateVolume,42 "playlist-pos": state.updatePosition,43 "playlist-count": state.updatePlaylist,44 }4546 for property, observer := range observers {47 ch, err := mpv.ObserveProperty(property)48 if err != nil {49 return nil, err50 }5152 go observer(ch)53 }5455 return state, nil56}5758func (p *playerState) ErrChan() <-chan error {59 return p.errChan60}6162func (p *playerState) updateState(ch <-chan interface{}) {63 for data := range ch {64 p.stateCond.L.Lock()65 p.playing = !(data.(bool))66 p.stateCond.Broadcast()67 p.stateCond.L.Unlock()68 }69}7071func (p *playerState) updateVolume(ch <-chan interface{}) {72 for data := range ch {73 vol := data.(float64)7475 p.volCond.L.Lock()76 p.volume = uint(vol)77 p.volCond.Broadcast()78 p.volCond.L.Unlock()79 }80}8182func (p *playerState) updatePosition(ch <-chan interface{}) {83 for data := range ch {84 p.posCond.L.Lock()85 pos := data.(float64)86 p.pos = int(pos)8788 p.posCond.Broadcast()89 p.posCond.L.Unlock()90 }91}9293// XXX: This implementation assumes that the playlist is never cleared.94func (p *playerState) updatePlaylist(ch <-chan interface{}) {95 for data := range ch {96 newCount := int(data.(float64))97 if newCount < 0 {98 panic("unreachable")99 }100101 p.playCond.L.Lock()102 oldCount := len(p.playlist)103 diff := newCount - oldCount104105 for i := 0; i < diff; i++ {106 entry, err := p.song(oldCount + i)107 if err != nil {108 p.errChan <- err109 continue110 }111 p.playlist = append(p.playlist, entry)112 }113114 p.playCond.Broadcast()115 p.playCond.L.Unlock()116 }117}118119func (p *playerState) song(idx int) (string, error) {120 nameProp := fmt.Sprintf("playlist/%d/filename", idx)121 name, err := p.mpv.GetProperty(nameProp)122 if err != nil {123 return "", err124 }125126 titleProp := fmt.Sprintf("playlist/%d/title", idx)127 title, err := p.mpv.GetProperty(titleProp)128 if err != nil {129 // Property may not be available130 return name.(string), nil131 }132133 // TODO: What if the title contains single qoutes?134 return fmt.Sprintf("%s %s", name, title), nil135}136137func (p *playerState) Playing() bool {138 p.stateCond.L.Lock()139 r := p.playing140 p.stateCond.L.Unlock()141142 return r143}144145func (p *playerState) WaitPlaying() bool {146 p.stateCond.L.Lock()147 oldState := p.playing148 for oldState == p.playing {149 p.stateCond.Wait()150 }151152 newState := p.playing153 p.stateCond.L.Unlock()154155 return newState156}157158// Index returns the current position on the playlist, or159// -1 if there is no current entry (e.g. playlist is empty).160func (p *playerState) Index() int {161 p.posCond.L.Lock()162 r := p.pos163 p.posCond.L.Unlock()164165 return r166}167168func (p *playerState) WaitIndex() int {169 p.posCond.L.Lock()170 oldPos := p.pos171 for oldPos == p.pos {172 p.posCond.Wait()173 }174175 newPos := p.pos176 p.posCond.L.Unlock()177178 return newPos179}180181func (p *playerState) Volume() uint {182 p.volCond.L.Lock()183 vol := p.volume184 p.volCond.L.Unlock()185186 return vol187}188189func (p *playerState) WaitVolume() uint {190 p.volCond.L.Lock()191 oldvol := p.volume192 // TODO: What happens when `echo ${oldvol} >> playvol`?193 for oldvol == p.volume {194 p.volCond.Wait()195 }196197 vol := p.volume198 p.volCond.L.Unlock()199200 return vol201}202203func (p *playerState) Playlist() []string {204 p.playCond.L.Lock()205 r := p.playlist206 p.playCond.L.Unlock()207208 return r209}210211// WaitPlayist blocks until the playlist changes and returns the212// most recent entry added to the playlist.213func (p *playerState) WaitPlayist() string {214 oldlen := len(p.playlist)215 p.playCond.L.Lock()216 for len(p.playlist) <= oldlen {217 p.playCond.Wait()218 }219220 newIndex := len(p.playlist) - 1221 r := p.playlist[newIndex]222 p.playCond.L.Unlock()223224 return r225}