Skip to content

Commit

Permalink
Merge pull request #30 from jmillerv/4-podcast-support
Browse files Browse the repository at this point in the history
4 podcast support
  • Loading branch information
jmillerv authored Dec 11, 2022
2 parents 1ba8392 + 99dbace commit 2f84582
Show file tree
Hide file tree
Showing 10 changed files with 257 additions and 58 deletions.
18 changes: 8 additions & 10 deletions content/media.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,19 @@ import (
// content type should be able to be set from the configuration

const (
podcastContent MediaType = "podcast"
announcementContent MediaType = "announcement"
webRadioContent MediaType = "web_radio"
fileContent MediaType = "file"
folderContent MediaType = "folder"
podcastContent MediaType = "podcast"
webRadioContent MediaType = "web_radio"
fileContent MediaType = "file"
folderContent MediaType = "folder"
)

type MediaType string

var MediaTypeMap = map[MediaType]Media{
podcastContent: new(Podcast),
announcementContent: new(Announcement),
webRadioContent: new(WebRadio),
fileContent: new(LocalFile),
folderContent: new(Folder),
podcastContent: new(Podcast),
webRadioContent: new(WebRadio),
fileContent: new(LocalFile),
folderContent: new(Folder),
}

// Media is the interface to represent playing any type of audio.
Expand Down
107 changes: 99 additions & 8 deletions content/podcast.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,114 @@
package content

import log "github.com/sirupsen/logrus"
import (
"github.com/mmcdole/gofeed"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"os/exec"
)

const (
playOrderNewest PlayOrder = "newest"
playOrderOldest PlayOrder = "oldest"
playOrderRandom PlayOrder = "random"
)

var pods podcasts // holds the feed data for podcasts
var podcastStream streamPlayer

type Podcast struct {
Name string
URL string
Path string
Content []byte
Name string
URL string
Player streamPlayer
PlayOrder PlayOrder // options: newest, oldest, random
}

type PlayOrder string

// Get parses a podcast feed and sets the most recent episode as the Podcast content.
func (p *Podcast) Get() error {
panic("implement me")
var ep episode
parser := gofeed.NewParser()
feed, err := parser.ParseURL(p.URL)
if err != nil {
return err
}
// traverse links
for _, item := range feed.Items {
pods.Episodes = append(pods.Episodes, item)
}

switch p.PlayOrder {
case playOrderNewest:
ep = pods.getNewestEpisode()
break
case playOrderOldest:
log.Panic("implement me")
//ep = pods.getOldestEpisode()
case playOrderRandom:
log.Panic("implement me")
//ep = pods.getRandomEpisode()
}

// setup podcast stream
log.Infof("extension: %v", ep.EpExtension)
podcastStream.playerName = streamPlayerName
podcastStream.url = ep.EpURL
podcastStream.command = exec.Command(podcastStream.playerName, "-quiet", podcastStream.url)
podcastStream.in, err = podcastStream.command.StdinPipe()
if err != nil {
return errors.Wrap(err, "error creating standard pipe in")
}
podcastStream.out, err = podcastStream.command.StdoutPipe()
if err != nil {
return errors.Wrap(err, "error creating standard pipe out")
}

podcastStream.isPlaying = false

p.Player = podcastStream

return nil
}

// Play sends the audio to the output. It caches a played episode in the cache ofr later checks.
func (p *Podcast) Play() error {
panic("implement me")
log.Infof("streaming from %v ", p.URL)
if !p.Player.isPlaying {
err := p.Player.command.Start()
if err != nil {
return errors.Wrap(err, "error starting podcast streamPlayer")
}
p.Player.isPlaying = true
done := make(chan bool)
func() {
p.Player.pipeChan <- p.Player.out
done <- true
}()
<-done
}
return nil
}

func (p *Podcast) Stop() error {
log.Infof("Stopping stream from %v ", p.Path)
log.Infof("Stopping stream from %v ", p.URL)
if p.Player.isPlaying {
p.Player.isPlaying = false
_, err := p.Player.in.Write([]byte("q"))
if err != nil {
log.WithError(err).Error("error stopping web radio streamPlayerName: w.Player.in.Write()")
}
err = p.Player.in.Close()
if err != nil {
log.WithError(err).Error("error stopping web radio streamPlayerName: w.Player.in.Close()")
}
err = p.Player.out.Close()
if err != nil {
log.WithError(err).Error("error stopping web radio streamPlayerName: w.Player.out.Close()")
}
p.Player.command = nil

p.Player.url = ""
}
return nil
}
65 changes: 65 additions & 0 deletions content/podcast_episodes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package content

import (
"github.com/mmcdole/gofeed"
"math/rand"
"time"
)

type podcasts struct {
Episodes []*gofeed.Item
// add podcast cache
}

func (p *podcasts) getNewestEpisode() episode {
var newestEpisode episode
var date *time.Time
date = p.Episodes[0].PublishedParsed
for _, ep := range p.Episodes {
// TODO if played, log that it's in the cache, and skip to the next episode
if ep.PublishedParsed.After(*date) || ep.PublishedParsed.Equal(*date) {
date = ep.PublishedParsed
newestEpisode.Item = ep
newestEpisode.EpURL = ep.Enclosures[0].URL
newestEpisode.EpExtension = ep.Enclosures[0].Type
}
}
return newestEpisode
}

func (p *podcasts) getOldestEpisode() *episode {
var oldestEpisode *episode
var date *time.Time
// TODO if played, log that it's in the cache, and skip to the next episode
for _, ep := range p.Episodes {
date = p.Episodes[0].PublishedParsed
if ep.PublishedParsed.Before(*date) || ep.PublishedParsed.Equal(*date) {
date = ep.PublishedParsed
oldestEpisode.Item = ep
oldestEpisode.EpURL = ep.Enclosures[0].URL
oldestEpisode.EpExtension = ep.Enclosures[0].Type
}
}
return oldestEpisode
}

func (p *podcasts) getRandomEpisode() *episode {
var randomEpisode *episode
rand.Seed(time.Now().UnixNano())
item := p.Episodes[rand.Intn(len(p.Episodes))]
// TODO if played, log that it's in the cache, and skip to the next episode
randomEpisode.Item = item
randomEpisode.EpExtension = item.Enclosures[0].Type
randomEpisode.EpURL = item.Enclosures[0].URL
return randomEpisode
}

func (p *podcasts) checkIfPlayed(guid string) bool {
return false
}

type episode struct {
Item *gofeed.Item // Keep this to hold the additional data
EpExtension string
EpURL string
}
18 changes: 6 additions & 12 deletions content/podcast_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@ func TestPodcast_Get(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &Podcast{
Name: tt.fields.Name,
URL: tt.fields.URL,
Path: tt.fields.Path,
Content: tt.fields.Content,
Name: tt.fields.Name,
URL: tt.fields.URL,
}
if err := p.Get(); (err != nil) != tt.wantErr {
t.Errorf("Get() error = %v, wantErr %v", err, tt.wantErr)
Expand All @@ -51,10 +49,8 @@ func TestPodcast_Play(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &Podcast{
Name: tt.fields.Name,
URL: tt.fields.URL,
Path: tt.fields.Path,
Content: tt.fields.Content,
Name: tt.fields.Name,
URL: tt.fields.URL,
}
if err := p.Play(); (err != nil) != tt.wantErr {
t.Errorf("Play() error = %v, wantErr %v", err, tt.wantErr)
Expand All @@ -79,10 +75,8 @@ func TestPodcast_Stop(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &Podcast{
Name: tt.fields.Name,
URL: tt.fields.URL,
Path: tt.fields.Path,
Content: tt.fields.Content,
Name: tt.fields.Name,
URL: tt.fields.URL,
}
p.Stop()
})
Expand Down
9 changes: 6 additions & 3 deletions content/program.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ func (p *Program) GetMedia() Media {
func (p *Program) mediaFactory() Media {
m := MediaTypeMap[p.Type]
switch m.(type) {
case *Announcement:
panic("implement me")
case *Folder:
folder := m.(*Folder)
folder.Name = p.Name
Expand All @@ -35,7 +33,12 @@ func (p *Program) mediaFactory() Media {
log.Debugf("returning LocalFile: %v", formatter.StructToString(file))
return file
case *Podcast:
panic("implement me")
podcast := m.(*Podcast)
podcast.Name = p.Name
podcast.URL = p.Source
podcast.PlayOrder = "newest" // TODO: Add support for random, oldest, and set from PlayOrder from config.
log.Debugf("returning podcast: %v", formatter.StructToString(podcast))
return podcast
case *WebRadio:
radio := m.(*WebRadio)
radio.Name = p.Name
Expand Down
15 changes: 15 additions & 0 deletions content/program_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,21 @@ func TestProgram_GetMedia(t *testing.T) {
Type: "file",
}).GetMedia(),
},
{
name: "Success: returns podcast",
fields: fields{
program: &Program{
Name: "Tech Won't Save Us",
Source: "https://feeds.buzzsprout.com/1004689.rss",
Timeslot: &Timeslot{
Begin: "11:00PM",
End: "11:30PM",
},
Type: "podcast",
},
},
want: nil,
},
{
name: "Success: returns web radio",
fields: fields{
Expand Down
16 changes: 16 additions & 0 deletions content/stream.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package content

import (
"io"
"os/exec"
)

type streamPlayer struct {
playerName string
url string
isPlaying bool
command *exec.Cmd
in io.WriteCloser
out io.ReadCloser
pipeChan chan io.ReadCloser
}
Loading

0 comments on commit 2f84582

Please sign in to comment.