OnlineLibrary
196 строк · 3.7 Кб
1package player
2
3import (
4"bufio"
5"fmt"
6"io"
7"sync"
8"sync/atomic"
9"time"
10
11"OnlineLibrary/internal/minimp3"
12"OnlineLibrary/internal/sonic"
13"OnlineLibrary/internal/util/syncio"
14"OnlineLibrary/internal/waveout"
15)
16
17type Fragment struct {
18sync.Mutex
19paused bool
20stream *sonic.Stream
21dec *minimp3.Decoder
22pcmBytesPerSec int
23wpBufSize int
24Bitrate int
25wp *waveout.WavePlayer
26willBeStopped bool
27pos time.Duration
28}
29
30const BufferDuration = time.Millisecond * 400
31
32func NewFragment(src io.ReadSeeker, devName string) (*Fragment, error) {
33dec := minimp3.NewDecoder(src)
34// Reading into an empty buffer will fill the internal buffer of the decoder, so you can get the audio data parameters
35if _, err := dec.Read(nil); err != nil {
36return nil, err
37}
38
39sampleRate := dec.SampleRate()
40channels := dec.Channels()
41bitrate := dec.Bitrate()
42pcmBytesPerSec := sampleRate * channels * 2
43
44if pcmBytesPerSec == 0 || bitrate == 0 {
45return nil, fmt.Errorf("invalid mp3")
46}
47
48wpBufSize := int(time.Duration(pcmBytesPerSec) * BufferDuration / time.Second)
49wp, err := waveout.NewWavePlayer(channels, sampleRate, 16, wpBufSize, devName)
50if err != nil {
51return nil, err
52}
53
54f := &Fragment{
55pcmBytesPerSec: pcmBytesPerSec,
56wpBufSize: wpBufSize,
57Bitrate: bitrate,
58stream: sonic.NewStream(sampleRate, channels),
59dec: dec,
60wp: wp,
61}
62
63return f, nil
64}
65
66func (f *Fragment) play(playing *atomic.Bool, elapsedTimeCallback func(time.Duration)) error {
67var p time.Duration
68wp := bufio.NewWriterSize(f.wp, f.wpBufSize)
69stream := syncio.NewReadWriter(f.stream, f)
70dec := syncio.NewReader(f.dec, f)
71
72for playing.Load() {
73elapsedTimeCallback(f.Position())
74_, err := io.CopyN(stream, dec, int64(f.wpBufSize))
75if err != nil {
76if err != io.EOF {
77f.wp.Stop()
78return fmt.Errorf("copying from mp3 decoder to sonic stream: %w", err)
79}
80f.Lock()
81f.stream.Flush()
82f.Unlock()
83}
84if _, err := wp.ReadFrom(stream); err != nil {
85return fmt.Errorf("copying from sonic stream to wave player: %w", err)
86}
87f.Lock()
88if f.willBeStopped {
89p = 0
90f.willBeStopped = false
91} else {
92f.pos += p
93p = BufferDuration
94}
95f.Unlock()
96
97if err != nil {
98// Here err is always io.EOF
99wp.Flush()
100break
101}
102}
103
104f.wp.Sync()
105f.Lock()
106f.pos += p
107f.Unlock()
108elapsedTimeCallback(0)
109return nil
110}
111
112func (f *Fragment) setSpeed(speed float64) {
113f.Lock()
114defer f.Unlock()
115f.stream.SetSpeed(speed)
116}
117
118func (f *Fragment) setVolume(volume float64) {
119f.Lock()
120defer f.Unlock()
121f.stream.SetVolume(volume)
122}
123
124func (f *Fragment) SetPosition(pos time.Duration) error {
125f.Lock()
126defer f.Unlock()
127
128if pos < 0 {
129// Negative position means the beginning of the fragment
130pos = 0
131}
132
133if pos == f.pos {
134// Requested position has already been set
135return nil
136}
137
138f.wp.Stop()
139f.willBeStopped = true
140f.stream.Flush()
141io.ReadAll(f.stream)
142
143offset := int64(pos / (time.Second / time.Duration(f.pcmBytesPerSec)))
144_, err := f.dec.Seek(offset, io.SeekStart)
145if err != nil {
146return err
147}
148
149f.pos = pos
150return nil
151}
152
153func (f *Fragment) Position() time.Duration {
154f.Lock()
155defer f.Unlock()
156return f.pos
157}
158
159func (f *Fragment) pause(pause bool) bool {
160f.Lock()
161defer f.Unlock()
162
163if f.paused == pause {
164return false
165}
166f.paused = pause
167
168f.wp.Pause(f.paused)
169return true
170}
171
172func (f *Fragment) IsPause() bool {
173f.Lock()
174defer f.Unlock()
175return f.paused
176}
177
178func (f *Fragment) SetOutputDevice(devName string) error {
179return f.wp.SetOutputDevice(devName)
180}
181
182func (f *Fragment) stop() {
183f.wp.Stop()
184}
185
186func (f *Fragment) Close() error {
187f.Lock()
188defer f.Unlock()
189f.wp.Stop()
190err := f.wp.Close()
191f.stream.Flush()
192io.ReadAll(f.stream)
193f.stream = nil
194f.dec = nil
195return err
196}
197