aboutsummaryrefslogtreecommitdiff
path: root/music/playqueue.go
diff options
context:
space:
mode:
Diffstat (limited to 'music/playqueue.go')
-rw-r--r--music/playqueue.go158
1 files changed, 158 insertions, 0 deletions
diff --git a/music/playqueue.go b/music/playqueue.go
new file mode 100644
index 0000000..d6182ed
--- /dev/null
+++ b/music/playqueue.go
@@ -0,0 +1,158 @@
1package music
2
3import (
4 "fmt"
5 "path/filepath"
6 "time"
7
8 "github.com/delucks/go-subsonic"
9 "github.com/faiface/beep"
10 "github.com/faiface/beep/flac"
11 "github.com/faiface/beep/mp3"
12 "github.com/faiface/beep/speaker"
13 "github.com/faiface/beep/vorbis"
14)
15
16type Queue struct {
17 songs []*subsonic.Child
18 isPaused bool
19
20 sub *subsonic.Client
21 speakerInitialized bool
22 oldSampleRate beep.SampleRate
23 onChange func(newSong *subsonic.Child, isPaused bool)
24}
25
26func NewQueue(client *subsonic.Client) *Queue {
27 return &Queue{
28 sub: client,
29 speakerInitialized: false,
30 }
31}
32
33func (q *Queue) SetClient(client *subsonic.Client) {
34 q.Clear()
35 q.sub = client
36}
37
38func (p *Queue) GetSongs() []*subsonic.Child {
39 return p.songs
40}
41
42func (q *Queue) Append(s *subsonic.Child) {
43 q.songs = append(q.songs, s)
44}
45
46func (q *Queue) Clear() {
47 q.songs = make([]*subsonic.Child, 0)
48 speaker.Clear()
49}
50
51func (q *Queue) PlaySong(s *subsonic.Child) error {
52 rc, err := Download2(q.sub, s.ID)
53 if err != nil {
54 return err
55 }
56
57 var ssc beep.StreamSeekCloser
58 var format beep.Format
59
60 switch filepath.Ext(s.Path) {
61 case ".mp3":
62 ssc, format, err = mp3.Decode(rc)
63 case ".ogg":
64 fallthrough
65 case ".oga":
66 ssc, format, err = vorbis.Decode(rc)
67 case ".flac":
68 ssc, format, err = flac.Decode(rc)
69 default:
70 return fmt.Errorf("unknown file type: %s", filepath.Ext(s.Path))
71 }
72
73 if err != nil {
74 return err
75 }
76
77 streamer, err := q.setupSpeaker(ssc, format)
78 if err != nil {
79 return err
80 }
81 speaker.Clear()
82 speaker.Play(beep.Seq(streamer, beep.Callback(func() { go q.Next() })))
83
84 if q.onChange != nil {
85 q.onChange(s, false)
86 }
87
88 return nil
89}
90
91func (q *Queue) Play() error {
92 if len(q.songs) == 0 {
93 return fmt.Errorf("the queue is empty")
94 }
95
96 s := q.songs[0]
97 q.PlaySong(s)
98
99 return nil
100}
101
102func (q *Queue) Next() error {
103 q.Stop()
104
105 if len(q.songs) == 0 {
106 return nil
107 }
108
109 q.songs = q.songs[1:]
110
111 if len(q.songs) == 0 {
112 if q.onChange != nil {
113 q.onChange(nil, false)
114 }
115 return nil
116 }
117
118 return q.Play()
119}
120
121func (q *Queue) Stop() {
122 speaker.Clear()
123}
124
125func (q *Queue) SetOnChangeCallback(f func(newSong *subsonic.Child, isPlaying bool)) {
126 q.onChange = f
127}
128
129func (q *Queue) TogglePause() {
130 if q.isPaused {
131 speaker.Unlock()
132 } else {
133 speaker.Lock()
134 }
135
136 q.isPaused = !q.isPaused
137
138 if q.onChange != nil {
139 if len(q.songs) > 0 {
140 q.onChange(q.songs[0], q.isPaused)
141 }
142 }
143}
144
145func (p *Queue) setupSpeaker(s beep.Streamer, format beep.Format) (beep.Streamer, error) {
146 if !p.speakerInitialized {
147 err := speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
148 if err != nil {
149 return nil, fmt.Errorf("speaker.Init: %v", err)
150 }
151 p.speakerInitialized = true
152 p.oldSampleRate = format.SampleRate
153
154 return s, nil
155 } else {
156 return beep.Resample(4, format.SampleRate, p.oldSampleRate, s), nil
157 }
158}