mpvfs

9P file server for controlling mpv playback

git clone https://git.8pit.net/mpvfs.git

  1package main
  2
  3import (
  4	"github.com/nmeum/mpvfs/mpv"
  5
  6	"fmt"
  7	"sync"
  8)
  9
 10type playerState struct {
 11	mpv *mpv.Client
 12
 13	volume  uint
 14	volCond *sync.Cond
 15
 16	playlist []string
 17	playCond *sync.Cond
 18
 19	stateCond *sync.Cond
 20	playing   bool
 21
 22	posCond *sync.Cond
 23	pos     int
 24
 25	errChan chan error
 26}
 27
 28func 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	}
 38
 39	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	}
 45
 46	for property, observer := range observers {
 47		ch, err := mpv.ObserveProperty(property)
 48		if err != nil {
 49			return nil, err
 50		}
 51
 52		go observer(ch)
 53	}
 54
 55	return state, nil
 56}
 57
 58func (p *playerState) ErrChan() <-chan error {
 59	return p.errChan
 60}
 61
 62func (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}
 70
 71func (p *playerState) updateVolume(ch <-chan interface{}) {
 72	for data := range ch {
 73		vol := data.(float64)
 74
 75		p.volCond.L.Lock()
 76		p.volume = uint(vol)
 77		p.volCond.Broadcast()
 78		p.volCond.L.Unlock()
 79	}
 80}
 81
 82func (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)
 87
 88		p.posCond.Broadcast()
 89		p.posCond.L.Unlock()
 90	}
 91}
 92
 93// 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		}
100
101		p.playCond.L.Lock()
102		oldCount := len(p.playlist)
103		diff := newCount - oldCount
104
105		for i := 0; i < diff; i++ {
106			entry, err := p.song(oldCount + i)
107			if err != nil {
108				p.errChan <- err
109				continue
110			}
111			p.playlist = append(p.playlist, entry)
112		}
113
114		p.playCond.Broadcast()
115		p.playCond.L.Unlock()
116	}
117}
118
119func (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 "", err
124	}
125
126	titleProp := fmt.Sprintf("playlist/%d/title", idx)
127	title, err := p.mpv.GetProperty(titleProp)
128	if err != nil {
129		// Property may not be available
130		return name.(string), nil
131	}
132
133	// TODO: What if the title contains single qoutes?
134	return fmt.Sprintf("%s %s", name, title), nil
135}
136
137func (p *playerState) Playing() bool {
138	p.stateCond.L.Lock()
139	r := p.playing
140	p.stateCond.L.Unlock()
141
142	return r
143}
144
145func (p *playerState) WaitPlaying() bool {
146	p.stateCond.L.Lock()
147	oldState := p.playing
148	for oldState == p.playing {
149		p.stateCond.Wait()
150	}
151
152	newState := p.playing
153	p.stateCond.L.Unlock()
154
155	return newState
156}
157
158// Index returns the current position on the playlist, or
159// -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.pos
163	p.posCond.L.Unlock()
164
165	return r
166}
167
168func (p *playerState) WaitIndex() int {
169	p.posCond.L.Lock()
170	oldPos := p.pos
171	for oldPos == p.pos {
172		p.posCond.Wait()
173	}
174
175	newPos := p.pos
176	p.posCond.L.Unlock()
177
178	return newPos
179}
180
181func (p *playerState) Volume() uint {
182	p.volCond.L.Lock()
183	vol := p.volume
184	p.volCond.L.Unlock()
185
186	return vol
187}
188
189func (p *playerState) WaitVolume() uint {
190	p.volCond.L.Lock()
191	oldvol := p.volume
192	// TODO: What happens when `echo ${oldvol} >> playvol`?
193	for oldvol == p.volume {
194		p.volCond.Wait()
195	}
196
197	vol := p.volume
198	p.volCond.L.Unlock()
199
200	return vol
201}
202
203func (p *playerState) Playlist() []string {
204	p.playCond.L.Lock()
205	r := p.playlist
206	p.playCond.L.Unlock()
207
208	return r
209}
210
211// WaitPlayist blocks until the playlist changes and returns the
212// 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	}
219
220	newIndex := len(p.playlist) - 1
221	r := p.playlist[newIndex]
222	p.playCond.L.Unlock()
223
224	return r
225}