diff --git a/go.mod b/go.mod index 1a6ae4e..cbd9f47 100644 --- a/go.mod +++ b/go.mod @@ -6,5 +6,6 @@ require ( fyne.io/fyne/v2 v2.4.2 github.com/ebitengine/oto/v3 v3.1.0 // indirect github.com/ebitengine/purego v0.5.1 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect golang.org/x/sys v0.15.0 // indirect ) diff --git a/go.sum b/go.sum index 0cb7eb3..b4ce960 100644 --- a/go.sum +++ b/go.sum @@ -243,6 +243,8 @@ github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= @@ -502,6 +504,7 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/main.go b/main.go index 18e6e90..87df1a0 100644 --- a/main.go +++ b/main.go @@ -36,7 +36,6 @@ package main */ import ( - "bufio" "encoding/json" "flag" "image" @@ -51,6 +50,7 @@ import ( "time" "github.com/ebitengine/oto/v3" + "github.com/muesli/cancelreader" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" @@ -61,6 +61,21 @@ import ( "fyne.io/fyne/v2/widget" ) +// Enums and constants +const RADIOSPIRAL_STREAM = "https://radiospiral.radio/stream.mp3" +const RADIOSPIRAL_JSON_ENDPOINT = "https://radiospiral.net/wp-json/radio/broadcast" + +const ( + Loading int = iota + Playing + Paused + Stopped +) + +// Cancel Reader + +var reader cancelreader.CancelReader + // helper func check(err error) { if err != nil { @@ -220,7 +235,6 @@ func (player *StreamPlayer) Close() { player.in.Close() player.out.Close() player.audio.Close() - // player.command.Cancel() player.stream_url = "" } @@ -240,13 +254,8 @@ func (player *StreamPlayer) Mute() { func (player *StreamPlayer) Pause() { if player.IsPlaying() { if !player.paused { - log.Println("[oto] Pausing") player.paused = true player.otoPlayer.Pause() - } else { - log.Println("[oto] Playing") - player.paused = false - player.otoPlayer.Play() } } } @@ -284,8 +293,6 @@ func loadImageURL(url string) image.Image { } func main() { - const RADIOSPIRAL_STREAM = "https://radiospiral.radio/stream.mp3" - const RADIOSPIRAL_JSON_ENDPOINT = "https://radiospiral.net/wp-json/radio/broadcast" PLAYER_CMD := "ffmpeg" if runtime.GOOS == "windows" { @@ -313,9 +320,6 @@ func main() { // Create our StreamPlayer instance streamPlayer := StreamPlayer{player_name: PLAYER_CMD, pipe_chan: pipe_chan} - // Make sure that StreamPlayer closes when the program ends - defer streamPlayer.Close() - // Create our app and window app := app.New() window := app.NewWindow("RadioSpiral Player") @@ -324,7 +328,7 @@ func main() { window.SetIcon(resourceIconPng) // Keep the status of the player - playStatus := false + playStatus := Stopped // Placeholder avatar radioSpiralAvatar := loadImageURL("https://radiospiral.net/wp-content/uploads/2018/03/Radio-Spiral-Logo-1.png") @@ -343,7 +347,6 @@ func main() { // Player section nowPlayingLabelHeader := widget.NewLabel("Now playing:") nowPlayingLabel := widget.NewLabel("") - var playButton *widget.Button volumeDown := widget.NewButtonWithIcon("", theme.VolumeDownIcon(), func() { streamPlayer.DecVolume() }) @@ -364,34 +367,65 @@ func main() { nowPlayingLabel.Alignment = fyne.TextAlignCenter nowPlayingLabel.Wrapping = fyne.TextWrapWord - // Process the output of Mplayer here in a separate goroutine + var playButton *widget.Button + + playButton = widget.NewButtonWithIcon("", theme.MediaPlayIcon(), func() { + // Here we control each time the button is pressed and update its + // appearance anytime it is clicked. We make the player start playing + // or pause. + if !streamPlayer.IsPlaying() && !streamPlayer.paused { + playButton.SetIcon(theme.MediaPauseIcon()) + playButton.SetText("(Buffering)") + streamPlayer.Load(RADIOSPIRAL_STREAM) + streamPlayer.Play() + playStatus = Loading + } else { + if playStatus == Playing { + playStatus = Paused + playButton.SetIcon(theme.MediaPlayIcon()) + streamPlayer.Pause() + } else { + reader.Cancel() + playStatus = Loading + playButton.SetText("(Buffering)") + playButton.SetIcon(theme.MediaPauseIcon()) + streamPlayer.Load(RADIOSPIRAL_STREAM) + streamPlayer.Play() + } + } + }) + + // Process the output of ffmpeg here in a separate goroutine go func() { for { out_pipe := <-pipe_chan - reader := bufio.NewReader(out_pipe) + var err error + reader, err = cancelreader.NewReader(out_pipe) + if err != nil { + log.Println("Error opening reader") + } for { - data, err := reader.ReadString('\n') + var data [255]byte + _, err := reader.Read(data[:]) if err != nil { - log.Fatal(err) - log.Println("Reloading player") - streamPlayer.Close() - pipe_chan = make(chan io.ReadCloser) - streamPlayer = StreamPlayer{player_name: PLAYER_CMD, pipe_chan: pipe_chan} - streamPlayer.Load(RADIOSPIRAL_STREAM) - streamPlayer.Play() - playStatus = true - playButton.SetIcon(theme.MediaPlayIcon()) - defer streamPlayer.Close() - } else { + log.Println(err) + break + } + lines := strings.Split(string(data[:]), "\n") + for _, line := range lines { // Log, if enabled, the output of StreamPlayer if *loggingToFilePtr { - log.Print("[" + streamPlayer.player_name + "] " + data) + log.Print("[" + streamPlayer.player_name + "] " + line) + } + if strings.Contains(line, "Output #0") { + playStatus = Playing + playButton.SetText("") } // Check if there's an updated title and reflect it on the // GUI - if strings.Contains(data, "StreamTitle: ") { + if strings.Contains(line, "StreamTitle: ") { log.Println("Found new stream title, updating GUI") - newTitleParts := strings.Split(data, "StreamTitle: ") + newTitleParts := strings.Split(line, "StreamTitle: ") nowPlayingLabel.SetText(newTitleParts[1]) } } @@ -399,27 +433,6 @@ func main() { } }() - playButton = widget.NewButtonWithIcon("", theme.MediaPlayIcon(), func() { - // Here we control each time the button is pressed and update its - // appearance anytime it is clicked. We make the player start playing - // or pause. - if !streamPlayer.IsPlaying() { - playButton.SetIcon(theme.MediaPauseIcon()) - streamPlayer.Load(RADIOSPIRAL_STREAM) - streamPlayer.Play() - playStatus = true - } else { - if playStatus { - playStatus = false - playButton.SetIcon(theme.MediaPlayIcon()) - } else { - playStatus = true - playButton.SetIcon(theme.MediaPauseIcon()) - } - streamPlayer.Pause() - } - }) - rsUrl, err := url.Parse("https://radiospiral.net") if err != nil { @@ -477,4 +490,5 @@ func main() { // Showtime! window.ShowAndRun() + streamPlayer.Close() }