diff options
author | Simon Garrelou <simon.garrelou@gmail.com> | 2022-12-08 22:35:17 +0100 |
---|---|---|
committer | Simon Garrelou <simon.garrelou@gmail.com> | 2022-12-08 22:35:17 +0100 |
commit | 7e4333dac70cdb003e71b6805ed4d81f18aa233a (patch) | |
tree | 26190c147141f765d69880d98d891bf50071e891 /music/playqueue.go | |
parent | 96cc5db2b4062ced82faf01ddac24abef04df343 (diff) | |
download | termsonic-7e4333dac70cdb003e71b6805ed4d81f18aa233a.tar.gz termsonic-7e4333dac70cdb003e71b6805ed4d81f18aa233a.zip |
Music playback working
Diffstat (limited to 'music/playqueue.go')
-rw-r--r-- | music/playqueue.go | 158 |
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 @@ | |||
1 | package music | ||
2 | |||
3 | import ( | ||
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 | |||
16 | type 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 | |||
26 | func NewQueue(client *subsonic.Client) *Queue { | ||
27 | return &Queue{ | ||
28 | sub: client, | ||
29 | speakerInitialized: false, | ||
30 | } | ||
31 | } | ||
32 | |||
33 | func (q *Queue) SetClient(client *subsonic.Client) { | ||
34 | q.Clear() | ||
35 | q.sub = client | ||
36 | } | ||
37 | |||
38 | func (p *Queue) GetSongs() []*subsonic.Child { | ||
39 | return p.songs | ||
40 | } | ||
41 | |||
42 | func (q *Queue) Append(s *subsonic.Child) { | ||
43 | q.songs = append(q.songs, s) | ||
44 | } | ||
45 | |||
46 | func (q *Queue) Clear() { | ||
47 | q.songs = make([]*subsonic.Child, 0) | ||
48 | speaker.Clear() | ||
49 | } | ||
50 | |||
51 | func (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 | |||
91 | func (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 | |||
102 | func (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 | |||
121 | func (q *Queue) Stop() { | ||
122 | speaker.Clear() | ||
123 | } | ||
124 | |||
125 | func (q *Queue) SetOnChangeCallback(f func(newSong *subsonic.Child, isPlaying bool)) { | ||
126 | q.onChange = f | ||
127 | } | ||
128 | |||
129 | func (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 | |||
145 | func (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 | } | ||