Skip to content

Commit

Permalink
Add rss support (#4)
Browse files Browse the repository at this point in the history
* Add rss support

* Fix the humanize episode length

* Add rss local cache support

* Use mplayer as an alternative player
  • Loading branch information
LinuxSuRen committed Nov 21, 2021
1 parent 57a022d commit 84de861
Show file tree
Hide file tree
Showing 20 changed files with 659 additions and 1,166 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,26 @@

Listen podcast via CLI.

## Get started
## Install

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

```shell
hd install goplay
```

## Get started

Start to listen:

```shell
goplay 开源面对面
```

## Restriction

* Rely on `mplayer` if the audio file format is not mp3

## Release

This project can be released via [linuxsuren-versions](https://github.com/linuxsuren/linuxsuren-versions).
154 changes: 154 additions & 0 deletions cmd/play.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package cmd

import (
"fmt"
"github.com/AlecAivazis/survey/v2"
"github.com/gdamore/tcell"
"github.com/linuxsuren/goplay/pkg/advanced_ui"
"github.com/linuxsuren/goplay/pkg/rss"
exec2 "github.com/linuxsuren/http-downloader/pkg/exec"
"github.com/spf13/cobra"
"os/exec"
"time"
)

func NewPlayCommand() (cmd *cobra.Command) {
opt := &playOption{}

cmd = &cobra.Command{
Use: "goplay",
Example: "goplay opensource",
Short: "Play podcast",
Args: cobra.MinimumNArgs(1),
RunE: opt.runE,
}
return
}

type playOption struct {
}

func (o *playOption) runE(cmd *cobra.Command, args []string) (err error) {
keyword := args[0]

feeds, titles := rss.FindMapByKeyword(keyword)
if len(titles) == 0 {
err = fmt.Errorf("no podcast found by keyword: %s", keyword)
return
}
selector := &survey.Select{
Message: "Select a podcast",
Options: titles,
}
var choose string
if err = survey.AskOne(selector, &choose); err != nil {
return
}

feed := feeds[choose]
var episodes map[string]rss.Episode
episodes, titles = rss.ConvertEpisodeMap(feed.Items)
if len(titles) == 0 {
err = fmt.Errorf("no episode found by keyword: %s", keyword)
return
}
selector = &survey.Select{
Message: "Select an episode",
Options: titles,
}
if err = survey.AskOne(selector, &choose); err != nil {
return
}

episode := episodes[choose]
if !isSupport(episode) {
var ok bool
if ok, err = playWithPotentialTools(episode); !ok || err != nil {
err = fmt.Errorf("currently, only support mp3")
}
} else {
err = play(episode)
}
return
}

func isSupport(episode rss.Episode) bool {
return episode.Type == "audio/mpeg"
}

func playWithPotentialTools(episode rss.Episode) (ok bool, err error) {
var mplayer string
if mplayer, err = exec.LookPath("mplayer"); err == nil {
selector := &survey.Confirm{
Message: fmt.Sprintf("Do you want to play '%s' with mplayer?", episode.Title),
}

err = survey.AskOne(selector, &ok)
if err == nil && ok {
var audioCacheFile string
if audioCacheFile, err = advanced_ui.LoadAudioFile(episode.AudioLink); err == nil {
err = exec2.RunCommand(mplayer, audioCacheFile)
}
}
}
return
}

func play(episode rss.Episode) (err error) {
if _, err = advanced_ui.LoadAudioFile(episode.AudioLink); err != nil {
err = fmt.Errorf("failed to load audio file from: %s, error: %v", episode.AudioLink, err)
return
}

screen, err := tcell.NewScreen()
if err != nil {
panic(err)
}
err = screen.Init()
if err != nil {
panic(err)
}
defer screen.Fini()

var player *advanced_ui.TrackAudioPanel
if player, err = advanced_ui.NewTrackAudioPanel(episode); err != nil {
err = fmt.Errorf("failed to play '%s', error: %v", episode.Title, err)
return
}

screen.Clear()
player.Draw(screen)
screen.Show()

_ = player.Start()

seconds := time.Tick(time.Second)
events := make(chan tcell.Event)
go func() {
for {
events <- screen.PollEvent()
}
}()

loop:
for {
select {
case event := <-events:
changed, quit := player.Handle(event)
if quit {
player.Stop()
break loop
}
if changed {
screen.Clear()
player.Draw(screen)
screen.Show()
}
case <-seconds:
screen.Clear()
player.Draw(screen)
screen.Show()
}
}
return
}
10 changes: 8 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
module github.com/linuxsuren/goplay

go 1.14
go 1.16

require (
github.com/AlecAivazis/survey/v2 v2.3.2
github.com/SlyMarbo/rss v1.0.1
github.com/faiface/beep v1.1.0
github.com/gdamore/tcell v1.3.0
github.com/json-iterator/go v1.1.11
github.com/ghodss/yaml v1.0.0
github.com/linuxsuren/http-downloader v0.0.50
github.com/spf13/cobra v1.2.1
github.com/spf13/viper v1.9.0
github.com/stretchr/testify v1.7.0
)

replace github.com/SlyMarbo/rss v1.0.1 => github.com/LinuxSuRen/rss v1.0.2-0.20211120161457-3f8efe372d7a
264 changes: 256 additions & 8 deletions go.sum

Large diffs are not rendered by default.

125 changes: 2 additions & 123 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,130 +1,9 @@
package main

import (
"fmt"
"github.com/AlecAivazis/survey/v2"
"github.com/gdamore/tcell"
"github.com/linuxsuren/goplay/pkg/advanced_ui"
"github.com/linuxsuren/goplay/pkg/broadcast"
"os"
"time"
"github.com/linuxsuren/goplay/cmd"
)

func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: goplay keyword")
return
}
keyword := os.Args[1]

var albumID int
if result, err := broadcast.Search(keyword); err != nil {
panic(err)
} else if len(result.Data.AlbumViews.Albums) == 0 {
fmt.Println("not found any albums by keyword:", keyword)
return
} else {
albumTitle := make([]string, 0)
albumMap := make(map[string]int, 0)
for _, v := range result.Data.AlbumViews.Albums {
albumTitle = append(albumTitle, v.AlbumInfo.Title)
albumMap[v.AlbumInfo.Title] = v.AlbumInfo.ID
}

selector := &survey.Select{
Message: "Select a album",
Options: albumTitle,
}
var choose string
if err = survey.AskOne(selector, &choose); err != nil {
return
}
albumID = albumMap[choose]
}

tracks, err := broadcast.GetTrackList(albumID, 1, false)
if err != nil {
panic(err)
}

trackTitle := make([]string, 0)
trackMap := make(map[string]*broadcast.TrackInfo, 0)
for _, v := range tracks.Data.List {
trackTitle = append(trackTitle, v.Title)
trackMap[v.Title] = v
}

selector := &survey.Select{
Message: "Select a track",
Options: trackTitle,
}

var choose string
if err = survey.AskOne(selector, &choose); err == nil {
fmt.Println("start to play", choose)
trackInfo := trackMap[choose]
play(trackInfo)
}
}

func play(trackInfo *broadcast.TrackInfo) {
screen, err := tcell.NewScreen()
if err != nil {
panic(err)
}
err = screen.Init()
if err != nil {
panic(err)
}
defer screen.Fini()

var player *advanced_ui.TrackAudioPanel
if player, err = advanced_ui.NewTrackAudioPanel(trackInfo); err != nil {
panic(err)
}

//streamer, format, err := mp3.Decode(reader)
//if err != nil {
// panic(err)
//}
//defer streamer.Close()
//
//speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/30))

//ap := ui.NewAudioPanel(format.SampleRate, streamer)

screen.Clear()
player.Draw(screen)
screen.Show()

_ = player.Start()

seconds := time.Tick(time.Second)
events := make(chan tcell.Event)
go func() {
for {
events <- screen.PollEvent()
}
}()

loop:
for {
select {
case event := <-events:
changed, quit := player.Handle(event)
if quit {
player.Stop()
break loop
}
if changed {
screen.Clear()
player.Draw(screen)
screen.Show()
}
case <-seconds:
screen.Clear()
player.Draw(screen)
screen.Show()
}
}
_ = cmd.NewPlayCommand().Execute()
}

0 comments on commit 84de861

Please sign in to comment.