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}