Skip to content

Commit 84de861

Browse files
authored
Add rss support (#4)
* Add rss support * Fix the humanize episode length * Add rss local cache support * Use mplayer as an alternative player
1 parent 57a022d commit 84de861

File tree

20 files changed

+659
-1166
lines changed

20 files changed

+659
-1166
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,26 @@
22

33
Listen podcast via CLI.
44

5-
## Get started
5+
## Install
66

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

99
```shell
1010
hd install goplay
1111
```
1212

13+
## Get started
14+
1315
Start to listen:
1416

1517
```shell
1618
goplay 开源面对面
1719
```
1820

21+
## Restriction
22+
23+
* Rely on `mplayer` if the audio file format is not mp3
24+
1925
## Release
2026

2127
This project can be released via [linuxsuren-versions](https://github.com/linuxsuren/linuxsuren-versions).

cmd/play.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"github.com/AlecAivazis/survey/v2"
6+
"github.com/gdamore/tcell"
7+
"github.com/linuxsuren/goplay/pkg/advanced_ui"
8+
"github.com/linuxsuren/goplay/pkg/rss"
9+
exec2 "github.com/linuxsuren/http-downloader/pkg/exec"
10+
"github.com/spf13/cobra"
11+
"os/exec"
12+
"time"
13+
)
14+
15+
func NewPlayCommand() (cmd *cobra.Command) {
16+
opt := &playOption{}
17+
18+
cmd = &cobra.Command{
19+
Use: "goplay",
20+
Example: "goplay opensource",
21+
Short: "Play podcast",
22+
Args: cobra.MinimumNArgs(1),
23+
RunE: opt.runE,
24+
}
25+
return
26+
}
27+
28+
type playOption struct {
29+
}
30+
31+
func (o *playOption) runE(cmd *cobra.Command, args []string) (err error) {
32+
keyword := args[0]
33+
34+
feeds, titles := rss.FindMapByKeyword(keyword)
35+
if len(titles) == 0 {
36+
err = fmt.Errorf("no podcast found by keyword: %s", keyword)
37+
return
38+
}
39+
selector := &survey.Select{
40+
Message: "Select a podcast",
41+
Options: titles,
42+
}
43+
var choose string
44+
if err = survey.AskOne(selector, &choose); err != nil {
45+
return
46+
}
47+
48+
feed := feeds[choose]
49+
var episodes map[string]rss.Episode
50+
episodes, titles = rss.ConvertEpisodeMap(feed.Items)
51+
if len(titles) == 0 {
52+
err = fmt.Errorf("no episode found by keyword: %s", keyword)
53+
return
54+
}
55+
selector = &survey.Select{
56+
Message: "Select an episode",
57+
Options: titles,
58+
}
59+
if err = survey.AskOne(selector, &choose); err != nil {
60+
return
61+
}
62+
63+
episode := episodes[choose]
64+
if !isSupport(episode) {
65+
var ok bool
66+
if ok, err = playWithPotentialTools(episode); !ok || err != nil {
67+
err = fmt.Errorf("currently, only support mp3")
68+
}
69+
} else {
70+
err = play(episode)
71+
}
72+
return
73+
}
74+
75+
func isSupport(episode rss.Episode) bool {
76+
return episode.Type == "audio/mpeg"
77+
}
78+
79+
func playWithPotentialTools(episode rss.Episode) (ok bool, err error) {
80+
var mplayer string
81+
if mplayer, err = exec.LookPath("mplayer"); err == nil {
82+
selector := &survey.Confirm{
83+
Message: fmt.Sprintf("Do you want to play '%s' with mplayer?", episode.Title),
84+
}
85+
86+
err = survey.AskOne(selector, &ok)
87+
if err == nil && ok {
88+
var audioCacheFile string
89+
if audioCacheFile, err = advanced_ui.LoadAudioFile(episode.AudioLink); err == nil {
90+
err = exec2.RunCommand(mplayer, audioCacheFile)
91+
}
92+
}
93+
}
94+
return
95+
}
96+
97+
func play(episode rss.Episode) (err error) {
98+
if _, err = advanced_ui.LoadAudioFile(episode.AudioLink); err != nil {
99+
err = fmt.Errorf("failed to load audio file from: %s, error: %v", episode.AudioLink, err)
100+
return
101+
}
102+
103+
screen, err := tcell.NewScreen()
104+
if err != nil {
105+
panic(err)
106+
}
107+
err = screen.Init()
108+
if err != nil {
109+
panic(err)
110+
}
111+
defer screen.Fini()
112+
113+
var player *advanced_ui.TrackAudioPanel
114+
if player, err = advanced_ui.NewTrackAudioPanel(episode); err != nil {
115+
err = fmt.Errorf("failed to play '%s', error: %v", episode.Title, err)
116+
return
117+
}
118+
119+
screen.Clear()
120+
player.Draw(screen)
121+
screen.Show()
122+
123+
_ = player.Start()
124+
125+
seconds := time.Tick(time.Second)
126+
events := make(chan tcell.Event)
127+
go func() {
128+
for {
129+
events <- screen.PollEvent()
130+
}
131+
}()
132+
133+
loop:
134+
for {
135+
select {
136+
case event := <-events:
137+
changed, quit := player.Handle(event)
138+
if quit {
139+
player.Stop()
140+
break loop
141+
}
142+
if changed {
143+
screen.Clear()
144+
player.Draw(screen)
145+
screen.Show()
146+
}
147+
case <-seconds:
148+
screen.Clear()
149+
player.Draw(screen)
150+
screen.Show()
151+
}
152+
}
153+
return
154+
}

go.mod

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
module github.com/linuxsuren/goplay
22

3-
go 1.14
3+
go 1.16
44

55
require (
66
github.com/AlecAivazis/survey/v2 v2.3.2
7+
github.com/SlyMarbo/rss v1.0.1
78
github.com/faiface/beep v1.1.0
89
github.com/gdamore/tcell v1.3.0
9-
github.com/json-iterator/go v1.1.11
10+
github.com/ghodss/yaml v1.0.0
11+
github.com/linuxsuren/http-downloader v0.0.50
12+
github.com/spf13/cobra v1.2.1
1013
github.com/spf13/viper v1.9.0
14+
github.com/stretchr/testify v1.7.0
1115
)
16+
17+
replace github.com/SlyMarbo/rss v1.0.1 => github.com/LinuxSuRen/rss v1.0.2-0.20211120161457-3f8efe372d7a

go.sum

Lines changed: 256 additions & 8 deletions
Large diffs are not rendered by default.

main.go

Lines changed: 2 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,130 +1,9 @@
11
package main
22

33
import (
4-
"fmt"
5-
"github.com/AlecAivazis/survey/v2"
6-
"github.com/gdamore/tcell"
7-
"github.com/linuxsuren/goplay/pkg/advanced_ui"
8-
"github.com/linuxsuren/goplay/pkg/broadcast"
9-
"os"
10-
"time"
4+
"github.com/linuxsuren/goplay/cmd"
115
)
126

137
func main() {
14-
if len(os.Args) < 2 {
15-
fmt.Println("Usage: goplay keyword")
16-
return
17-
}
18-
keyword := os.Args[1]
19-
20-
var albumID int
21-
if result, err := broadcast.Search(keyword); err != nil {
22-
panic(err)
23-
} else if len(result.Data.AlbumViews.Albums) == 0 {
24-
fmt.Println("not found any albums by keyword:", keyword)
25-
return
26-
} else {
27-
albumTitle := make([]string, 0)
28-
albumMap := make(map[string]int, 0)
29-
for _, v := range result.Data.AlbumViews.Albums {
30-
albumTitle = append(albumTitle, v.AlbumInfo.Title)
31-
albumMap[v.AlbumInfo.Title] = v.AlbumInfo.ID
32-
}
33-
34-
selector := &survey.Select{
35-
Message: "Select a album",
36-
Options: albumTitle,
37-
}
38-
var choose string
39-
if err = survey.AskOne(selector, &choose); err != nil {
40-
return
41-
}
42-
albumID = albumMap[choose]
43-
}
44-
45-
tracks, err := broadcast.GetTrackList(albumID, 1, false)
46-
if err != nil {
47-
panic(err)
48-
}
49-
50-
trackTitle := make([]string, 0)
51-
trackMap := make(map[string]*broadcast.TrackInfo, 0)
52-
for _, v := range tracks.Data.List {
53-
trackTitle = append(trackTitle, v.Title)
54-
trackMap[v.Title] = v
55-
}
56-
57-
selector := &survey.Select{
58-
Message: "Select a track",
59-
Options: trackTitle,
60-
}
61-
62-
var choose string
63-
if err = survey.AskOne(selector, &choose); err == nil {
64-
fmt.Println("start to play", choose)
65-
trackInfo := trackMap[choose]
66-
play(trackInfo)
67-
}
68-
}
69-
70-
func play(trackInfo *broadcast.TrackInfo) {
71-
screen, err := tcell.NewScreen()
72-
if err != nil {
73-
panic(err)
74-
}
75-
err = screen.Init()
76-
if err != nil {
77-
panic(err)
78-
}
79-
defer screen.Fini()
80-
81-
var player *advanced_ui.TrackAudioPanel
82-
if player, err = advanced_ui.NewTrackAudioPanel(trackInfo); err != nil {
83-
panic(err)
84-
}
85-
86-
//streamer, format, err := mp3.Decode(reader)
87-
//if err != nil {
88-
// panic(err)
89-
//}
90-
//defer streamer.Close()
91-
//
92-
//speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/30))
93-
94-
//ap := ui.NewAudioPanel(format.SampleRate, streamer)
95-
96-
screen.Clear()
97-
player.Draw(screen)
98-
screen.Show()
99-
100-
_ = player.Start()
101-
102-
seconds := time.Tick(time.Second)
103-
events := make(chan tcell.Event)
104-
go func() {
105-
for {
106-
events <- screen.PollEvent()
107-
}
108-
}()
109-
110-
loop:
111-
for {
112-
select {
113-
case event := <-events:
114-
changed, quit := player.Handle(event)
115-
if quit {
116-
player.Stop()
117-
break loop
118-
}
119-
if changed {
120-
screen.Clear()
121-
player.Draw(screen)
122-
screen.Show()
123-
}
124-
case <-seconds:
125-
screen.Clear()
126-
player.Draw(screen)
127-
screen.Show()
128-
}
129-
}
8+
_ = cmd.NewPlayCommand().Execute()
1309
}

0 commit comments

Comments
 (0)