Skip to content

Commit 57a022d

Browse files
authored
Support to cache audio to local, reduce the memory consume (#3)
* Support to cache audio to local, reduce the memory consume * Update readme file about how to release
1 parent ad41387 commit 57a022d

File tree

4 files changed

+132
-16
lines changed

4 files changed

+132
-16
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,18 @@ Listen podcast via CLI.
44

55
## Get started
66

7-
Install via [goget](https://github.com/linuxsuren/goget):
7+
Install via [hd](https://github.com/linuxsuren/http-downloader):
88

99
```shell
10-
goget github.com/linuxsuren/goplay
10+
hd install goplay
1111
```
1212

1313
Start to listen:
1414

1515
```shell
1616
goplay 开源面对面
1717
```
18+
19+
## Release
20+
21+
This project can be released via [linuxsuren-versions](https://github.com/linuxsuren/linuxsuren-versions).

pkg/advanced_ui/track.go

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import (
1111
"github.com/linuxsuren/goplay/pkg/ui"
1212
"github.com/spf13/viper"
1313
"io"
14+
"io/ioutil"
1415
"net/http"
1516
"os"
17+
"path"
1618
"strconv"
1719
"time"
1820
)
@@ -26,11 +28,8 @@ type TrackAudioPanel struct {
2628
func NewTrackAudioPanel(trackInfo *broadcast.TrackInfo) (panel *TrackAudioPanel, err error) {
2729
_ = loadConfig()
2830

29-
var resp *http.Response
30-
if resp, err = http.Get(trackInfo.PlayURL64); err == nil {
31-
data, _ := io.ReadAll(resp.Body)
32-
buffer := bytes.NewReader(data)
33-
31+
var buffer io.Reader
32+
if buffer, err = playWithLocalCache(trackInfo.PlayURL64); err == nil {
3433
var streamer beep.StreamSeekCloser
3534
var format beep.Format
3635
streamer, format, err = mp3.Decode(playio.SeekerWithoutCloser(buffer))
@@ -41,13 +40,52 @@ func NewTrackAudioPanel(trackInfo *broadcast.TrackInfo) (panel *TrackAudioPanel,
4140
speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/30))
4241

4342
return &TrackAudioPanel{
44-
AudioPlayer: ui.NewAudioPanel(format.SampleRate, streamer),
43+
AudioPlayer: ui.NewAudioPanel(format.SampleRate, streamer, trackInfo.Title),
4544
audioUID: strconv.Itoa(trackInfo.UID),
4645
}, nil
4746
}
4847
return
4948
}
5049

50+
func playWithLocalCache(trackURL string) (reader io.Reader, err error) {
51+
if reader, err = playWithRange(trackURL, -1); err != nil {
52+
return
53+
}
54+
55+
var data []byte
56+
if data, err = ioutil.ReadAll(reader); err != nil {
57+
return
58+
}
59+
60+
cache := path.Join(os.TempDir(), "1")
61+
if err = ioutil.WriteFile(cache, data, 0600); err != nil {
62+
return
63+
}
64+
65+
reader, err = os.Open(cache)
66+
return
67+
}
68+
69+
func playWithRange(trackURL string, from int) (reader io.Reader, err error) {
70+
var resp *http.Response
71+
if resp, err = http.Get(trackURL); err == nil {
72+
ranges := resp.Header.Get("Accept-Ranges")
73+
length := resp.Header.Get("Content-Length")
74+
75+
if ranges == "bytes" && from >= 0 {
76+
lenghtNum, _ := strconv.Atoi(length)
77+
reader = playio.NewRangeReader(0, lenghtNum, trackURL)
78+
} else {
79+
var resp *http.Response
80+
if resp, err = http.Get(trackURL); err == nil {
81+
data, _ := io.ReadAll(resp.Body)
82+
reader = bytes.NewReader(data)
83+
}
84+
}
85+
}
86+
return
87+
}
88+
5189
func loadConfig() (err error) {
5290
viper.SetConfigName("goplay")
5391
viper.SetConfigType("yaml")

pkg/io/reader.go

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package io
22

3-
import "io"
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
)
49

510
// SeekableReader represents a reader that be able to seek
611
type SeekableReader interface {
@@ -18,9 +23,55 @@ type nopCloser struct {
1823
io.Reader
1924
}
2025

21-
func (a nopCloser) Seek(offset int64, whence int) (int64, error) {
26+
func (a nopCloser) Seek(offset int64, whence int) (int64, error) {
2227
seeker := a.Reader.(io.Seeker)
2328
return seeker.Seek(offset, whence)
2429
}
2530

2631
func (nopCloser) Close() error { return nil }
32+
33+
type RangeReader struct {
34+
offset int
35+
length int
36+
bufferSize int
37+
38+
resource string
39+
}
40+
41+
func NewRangeReader(offset, lenght int, resource string) *RangeReader {
42+
return &RangeReader{
43+
offset: offset,
44+
length: lenght,
45+
bufferSize: 0,
46+
resource: resource,
47+
}
48+
}
49+
50+
func (r *RangeReader) Read(p []byte) (n int, err error) {
51+
if r.bufferSize == 0 {
52+
r.bufferSize = len(p)
53+
}
54+
55+
client := http.DefaultClient
56+
57+
var request *http.Request
58+
if request, err = http.NewRequest(http.MethodGet, r.resource, nil); err != nil {
59+
return
60+
}
61+
62+
request.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", r.offset, r.offset+r.bufferSize))
63+
64+
var resp *http.Response
65+
if resp, err = client.Do(request); err != nil {
66+
return
67+
}
68+
69+
if resp.StatusCode == http.StatusPartialContent {
70+
if n, err = resp.Body.Read(p); err == nil {
71+
r.offset += r.bufferSize
72+
}
73+
} else {
74+
return 0, errors.New(fmt.Sprintf("unspport status code: %d", resp.StatusCode))
75+
}
76+
return
77+
}

pkg/ui/audio_panel.go

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@ type audioPanel struct {
1717
ctrl *beep.Ctrl
1818
resampler *beep.Resampler
1919
volume *effects.Volume
20+
title string
2021
}
2122

2223
// NewAudioPanel creates a audio panel
23-
func NewAudioPanel(sampleRate beep.SampleRate, streamer beep.StreamSeeker) AudioPlayer {
24+
func NewAudioPanel(sampleRate beep.SampleRate, streamer beep.StreamSeeker, title string) AudioPlayer {
2425
ctrl := &beep.Ctrl{Streamer: beep.Loop(-1, streamer)}
2526
resampler := beep.ResampleRatio(4, 1, ctrl)
2627
volume := &effects.Volume{Streamer: resampler, Base: 2}
27-
return &audioPanel{sampleRate, streamer, ctrl, resampler, volume}
28+
return &audioPanel{sampleRate, streamer, ctrl, resampler, volume, title}
2829
}
2930

3031
// Play starts to play a audio
@@ -42,6 +43,31 @@ func (ap *audioPanel) Position() int {
4243
return ap.streamer.Position()
4344
}
4445

46+
type linePrinter struct {
47+
index int
48+
screen tcell.Screen
49+
style tcell.Style
50+
}
51+
52+
func (p *linePrinter) print(msg string) {
53+
drawTextLine(p.screen, 0, p.index, msg, p.style)
54+
p.index++
55+
}
56+
57+
func (ap *audioPanel) printInfoArea(screen tcell.Screen, mainStyle tcell.Style) {
58+
printer := &linePrinter{
59+
screen: screen,
60+
style: mainStyle,
61+
}
62+
printer.print("Welcome to the GoPlay!")
63+
if ap.title != "" {
64+
printer.print(fmt.Sprintf("Title: %s", ap.title))
65+
}
66+
printer.print("Press [ESC] to quit.")
67+
printer.print("Press [SPACE] to pause/resume.")
68+
printer.print("Use keys in (?/?) to turn the buttons.")
69+
}
70+
4571
// Draw draws the infomation to a screen
4672
func (ap *audioPanel) Draw(screen tcell.Screen) {
4773
mainStyle := tcell.StyleDefault.
@@ -53,10 +79,7 @@ func (ap *audioPanel) Draw(screen tcell.Screen) {
5379

5480
screen.Fill(' ', mainStyle)
5581

56-
drawTextLine(screen, 0, 0, "Welcome to the Speedy Player!", mainStyle)
57-
drawTextLine(screen, 0, 1, "Press [ESC] to quit.", mainStyle)
58-
drawTextLine(screen, 0, 2, "Press [SPACE] to pause/resume.", mainStyle)
59-
drawTextLine(screen, 0, 3, "Use keys in (?/?) to turn the buttons.", mainStyle)
82+
ap.printInfoArea(screen, mainStyle)
6083

6184
speaker.Lock()
6285
position := ap.sampleRate.D(ap.streamer.Position())

0 commit comments

Comments
 (0)