382 lines
9.7 KiB
Go
382 lines
9.7 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/signal"
|
|
"runtime"
|
|
"time"
|
|
// "math"
|
|
"strconv"
|
|
|
|
"git.itmodulo.eu/LibrariesGo/lyrics-api-go-extended"
|
|
"github.com/TheCreeper/go-notify"
|
|
// "github.com/disintegration/imaging"
|
|
"github.com/diamondburned/gotk4/pkg/core/glib"
|
|
// "github.com/djherbis/fscache"
|
|
"github.com/gordonklaus/portaudio"
|
|
// "github.com/diamondburned/gotk4/pkg/gdk/v4"
|
|
// "github.com/delucks/go-subsonic"
|
|
"github.com/hraban/opus"
|
|
)
|
|
|
|
const rate float64 = 48000
|
|
const channels uint8 = 2
|
|
|
|
var PlayAudio bool = true // Used for play pause
|
|
var EndPlayer bool = false // Used for ending recursion
|
|
var StreamChanged bool = false
|
|
|
|
func PlayPort() {
|
|
|
|
nodeID := MainQueue[NowPlayingIndex]
|
|
songdata, _ := CurrentClient.GetSong(nodeID)
|
|
albmdata, _ := CurrentClient.GetAlbum(songdata.AlbumID)
|
|
|
|
// go glib.IdleAdd(FindSubtitles(songdata.Artist, songdata.Title))
|
|
TotalTime = songdata.Duration
|
|
|
|
// Async Gui update needed by GTK
|
|
|
|
var SetPlayingGuiData = func() {
|
|
MainArtistLabel.SetLabel(songdata.Artist)
|
|
MainSongTitleLabel.SetLabel(songdata.Title)
|
|
MainAlbumLabel.SetLabel(songdata.Album)
|
|
MainTotalTimeLabel.SetLabel(DurationToReadable(songdata.Duration))
|
|
|
|
DownloadCoverArtFullSize(albmdata.CoverArt)
|
|
var imgpath string = GetRunDir() + MainDir + DataDir + "/" + albmdata.CoverArt + ".jpg"
|
|
MainAlbumCover.SetFromFile(imgpath)
|
|
|
|
// path2 := os.UserHomeDir + MainDir + DataDir + "/" + albmdata.CoverArt + "-b.jpg"
|
|
|
|
// blu, err := os.Open(imgpath)
|
|
// if err != nil {
|
|
// LogOnError(err)
|
|
// }
|
|
// defer blu.Close()
|
|
|
|
// img, _, err := image.Decode(blu)
|
|
// if err != nil {
|
|
// LogOnError(err)
|
|
// }
|
|
|
|
// dstImage := imaging.Blur(img, MainSettings.BlurRadius)
|
|
// err = imaging.Save(dstImage, path2)
|
|
// if err != nil {
|
|
// LogOnError(err)
|
|
// }
|
|
|
|
// AD1
|
|
// MainAlbumCover.SetFromPixbuf(pixbuf)
|
|
|
|
MainPlayPauseButton.SetLabel("❚❚")
|
|
}
|
|
|
|
sig := make(chan os.Signal, 1)
|
|
signal.Notify(sig, os.Interrupt, os.Kill)
|
|
// param := map[string]string{
|
|
// "maxBitRate": strconv.Itoa(int(MainSettings.Bitrate)),
|
|
// "MainFormat": MainSettings.Codec,
|
|
// "transcodings": MainSettings.Codec,
|
|
// "estimateContentLength": "true",
|
|
// }
|
|
portaudio.Initialize()
|
|
defer portaudio.Terminate()
|
|
|
|
// create the cache, keys expire after 1 hour.
|
|
|
|
// var ReadMaster fscache.ReadAtCloser
|
|
// var WriteMaster io.WriteCloser
|
|
|
|
// IsCached := CurrentCache.Exists(nodeID)
|
|
|
|
ReadMaster, _, _ = CurrentCache.Get(nodeID)
|
|
|
|
// if !IsCached {
|
|
// reader, _ := CurrentClient.Stream(nodeID, param)
|
|
// go Backgroundread(reader, WriteMaster)
|
|
// } else {
|
|
// fmt.Println("Playing ", songdata.Artist, " - ", songdata.Title, " from cache")
|
|
// }
|
|
|
|
// Writing needs to be implemented in diffrenet place
|
|
// Here we use only reader
|
|
|
|
//AD2
|
|
|
|
OpusStr, err := opus.NewStream(ReadMaster)
|
|
chk(err)
|
|
result := <-AudioControlChan
|
|
fmt.Println("Result of channel is: ", result)
|
|
CurrentStream, _ := portaudio.OpenDefaultStream(0, 2, rate, portaudio.FramesPerBufferUnspecified, &Out)
|
|
// LogOnError(err)
|
|
EndPlayer = false
|
|
StreamChanged = true
|
|
PlayAudio = true
|
|
MainNotification := notify.NewNotification("Playing", songdata.Artist+": "+songdata.Title)
|
|
MainNotification.Timeout = 2000
|
|
if _, err := MainNotification.Show(); err != nil {
|
|
return
|
|
}
|
|
go glib.IdleAdd(SetPlayingGuiData)
|
|
MainAdjustment.Configure(float64(0), float64(0), float64(1), float64(1), float64(10), float64(0))
|
|
// chk(CurrentStream.Start())
|
|
|
|
for !EndPlayer {
|
|
if PlayAudio {
|
|
if StreamChanged {
|
|
CurrentStream.Start()
|
|
StreamChanged = false
|
|
}
|
|
_, err := OpusStr.Read(Out)
|
|
DrawnTime += float64(1920 / rate / 2)
|
|
glib.IdleAdd(UpdatePBar())
|
|
if (err == io.EOF) || (err == io.ErrUnexpectedEOF) || (TimeInPrecent >= 1.01) || (err == portaudio.StreamIsStopped) || (err == portaudio.TimedOut) {
|
|
fmt.Println("EOF", NowPlayingIndex, len(MainQueue)) // TODO Stram recover when unexpected
|
|
err = CurrentStream.Stop()
|
|
glib.IdleAdd(SetZeroTime())
|
|
defer CurrentStream.Close()
|
|
Out = make([]int16, 1920)
|
|
notify.CloseNotification(MainNotification.ReplacesID)
|
|
if NowPlayingIndex+1 < len(MainQueue) {
|
|
fmt.Println("Playing next song")
|
|
NowPlayingIndex++
|
|
go StartCaching(MainQueue[NowPlayingIndex])
|
|
if NowPlayingIndex+1 <= len(MainQueue)-1 {
|
|
// fmt.Println(songdata.Title, " +1 needs to be downloaded")
|
|
go CachingWaiter(MainQueue[NowPlayingIndex], MainQueue[NowPlayingIndex+1], 5)
|
|
}
|
|
if NowPlayingIndex+2 <= len(MainQueue)-1 {
|
|
// fmt.Println(songdata.Title, " +2 needs to be downloaded")
|
|
go CachingWaiter(MainQueue[NowPlayingIndex+1], MainQueue[NowPlayingIndex+2], 5)
|
|
}
|
|
go PlayPort() // Run new gouroutine to let func complete
|
|
EndPlayer = true
|
|
break
|
|
} else if (NowPlayingIndex + 1) == len(MainQueue) {
|
|
fmt.Println("End playing")
|
|
CurrentStream.Close()
|
|
EndPlayer = true
|
|
break
|
|
}
|
|
} else {
|
|
// if (err != portaudio.OutputUnderflowed && err != nil) {
|
|
// fmt.Println(err)
|
|
// }
|
|
err := CurrentStream.Write()
|
|
if err != nil {
|
|
// LogOnError(err)
|
|
}
|
|
|
|
}
|
|
} else {
|
|
StreamChanged = true
|
|
CurrentStream.Stop()
|
|
}
|
|
|
|
select {
|
|
case <-sig:
|
|
CurrentStream.Close()
|
|
EndPlayer = true
|
|
break
|
|
default:
|
|
}
|
|
|
|
}
|
|
PlayAudio = false
|
|
glib.IdleAdd(SetZeroTime())
|
|
fmt.Println("End playport")
|
|
AudioControlChan <- true
|
|
}
|
|
func Backgroundread(reader io.Reader, w io.WriteCloser, id string) {
|
|
fmt.Println("DEBUG: backgroundread ", id)
|
|
DownloadSyncGroup.Add(1)
|
|
readBuffer := make([]byte, MainSettings.DownladBufferSize)
|
|
for {
|
|
n, err := reader.Read(readBuffer)
|
|
if err != nil {
|
|
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
|
w.Write(readBuffer[:n])
|
|
break
|
|
} else {
|
|
fmt.Println(err)
|
|
}
|
|
}
|
|
w.Write(readBuffer[:n])
|
|
}
|
|
w.Close()
|
|
runtime.GC()
|
|
DownloadSyncGroup.Done()
|
|
fmt.Println("Ended downloading ", id)
|
|
CachingMap[id] = true
|
|
}
|
|
|
|
func CachingWaiter(idBlocking string, idNext string, waitTime int) {
|
|
isDownloaded, exists := CachingMap[idBlocking]
|
|
if !isDownloaded || !exists {
|
|
if waitTime == 0 {
|
|
waitTime = 3
|
|
}
|
|
time.Sleep(time.Duration(waitTime) * time.Second)
|
|
CachingWaiter(idBlocking, idNext, waitTime-1)
|
|
} else if isDownloaded && exists {
|
|
go StartCaching(idNext)
|
|
} else if !isDownloaded && exists {
|
|
return
|
|
}
|
|
}
|
|
|
|
func StartCaching(songID string) {
|
|
|
|
// songdata, _ := CurrentClient.GetSong(songID)
|
|
// albmdata, _ := CurrentClient.GetAlbum(songdata.AlbumID)
|
|
|
|
param := map[string]string{
|
|
"maxBitRate": strconv.Itoa(int(MainSettings.Bitrate)),
|
|
"MainFormat": MainSettings.Codec,
|
|
"transcodings": MainSettings.Codec,
|
|
"estimateContentLength": "true",
|
|
}
|
|
|
|
cacheExists := CurrentCache.Exists(songID)
|
|
_, WriteMaster, _ = CurrentCache.Get(songID)
|
|
|
|
if !cacheExists {
|
|
reader, _ := CurrentClient.Stream(songID, param)
|
|
// go DownloadCoverArtFullSize(albmdata.CoverArt)
|
|
Backgroundread(reader, WriteMaster, songID)
|
|
} else {
|
|
fmt.Println("Playing from cache")
|
|
}
|
|
}
|
|
|
|
// GTK Async gui update
|
|
func UpdatePBar() func() {
|
|
var up = func() {
|
|
TimeInPrecent = float64(DrawnTime / float64(TotalTime))
|
|
MainTimeLeft.SetText(DurationToReadable(TotalTime - int(float64(DrawnTime))))
|
|
MainTimeDrawnLabel.SetText(DurationToReadable(int(float64(DrawnTime))))
|
|
MainAdjustment.SetValue(float64(DrawnTime / float64(TotalTime)))
|
|
}
|
|
return up
|
|
}
|
|
|
|
// GTK Async gui update
|
|
func SetZeroTime() func() {
|
|
TotalTime = 0
|
|
DrawnTime = 0
|
|
TimeInPrecent = 0.0
|
|
var up = func() {
|
|
MainPlayPauseButton.SetLabel("▶")
|
|
MainTimeLeft.SetText(":")
|
|
MainTimeDrawnLabel.SetText(":")
|
|
MainAdjustment.SetValue(TimeInPrecent)
|
|
MainTotalTimeLabel.SetLabel(":")
|
|
MainArtistLabel.SetLabel(":")
|
|
MainSongTitleLabel.SetLabel(":")
|
|
MainAlbumLabel.SetLabel(":")
|
|
|
|
}
|
|
return up
|
|
}
|
|
|
|
func chk(err error) {
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
}
|
|
|
|
func DurationToReadable(dur int) string {
|
|
|
|
min := dur / 60
|
|
sec := dur % 60
|
|
|
|
if min >= 60 {
|
|
hr := dur / 3600
|
|
min = (dur - (hr * 3600)) / 60
|
|
sec = (dur - (hr * 3600) - (min * 60))
|
|
return (PrefixZeroLook(hr) + ":" + PrefixZeroLook(min) + ":" + PrefixZeroLook(sec))
|
|
}
|
|
return (PrefixZeroLook(min) + ":" + PrefixZeroLook(sec))
|
|
|
|
}
|
|
|
|
func PrefixZeroLook(localInt int) string {
|
|
if localInt < 10 {
|
|
return ("0" + strconv.Itoa(localInt))
|
|
} else {
|
|
return strconv.Itoa(localInt)
|
|
}
|
|
|
|
}
|
|
|
|
func Exists(name string) bool {
|
|
_, err := os.Stat(name)
|
|
if err == nil {
|
|
return true
|
|
}
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return false
|
|
}
|
|
return false
|
|
}
|
|
|
|
func FindSubtitles(artist string, song string) func() {
|
|
// TODO Repair
|
|
var fun = func() {
|
|
l := lyrics.New(*MetadataClient, lyrics.WithAllProviders())
|
|
lyric, err := l.Search(artist, song)
|
|
buf := MainSubtitlesTextView.Buffer()
|
|
if err != nil {
|
|
fmt.Printf("Lyrics for %v-%v were not found", artist, song)
|
|
buf.SetText("Not found")
|
|
}
|
|
buf.SetText(lyric)
|
|
MainSubtitlesTextView.SetBuffer(buf)
|
|
}
|
|
return fun
|
|
|
|
}
|
|
|
|
// AD1
|
|
// pixbuf2, err = gdk.PixbufNewFromFileAtSize(path2, w, h)
|
|
|
|
// gtk.LayoutNew()
|
|
|
|
// var css string = `
|
|
// GtkWindow {
|
|
// background-color: red;
|
|
// background-repeat: no-repeat;
|
|
// background-size: cover;
|
|
// background-position: center center;
|
|
// `
|
|
|
|
// var csscustom = `background-image: url("` + imgpath + `");
|
|
// }`
|
|
// var csscustom = `background-size: cover; background-color: red;
|
|
// }`
|
|
// fmt.Println(css + csscustom)
|
|
// prov, err := gtk.CssProviderNew()
|
|
// LogOnError(err)
|
|
// err = prov.LoadFromData(css + csscustom)
|
|
// LogOnError(err)
|
|
// scrn, err := gdk.ScreenGetDefault()
|
|
// LogOnError(err)
|
|
// gtk.AddProviderForScreen(scrn, prov, 1)
|
|
// MainWindow.ShowAll()
|
|
// LogFatalOnError(err)
|
|
|
|
// AD2
|
|
// path := os.UserHomeDir + MainDir + DataDir + "/" + nodeID.(string) + ".opus"
|
|
// var OpusStr *opus.Stream
|
|
// if !Exists(path) {
|
|
// f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
|
// chk(err)
|
|
// tee := io.TeeReader(reader, f)
|
|
// OpusStr, err = opus.NewStream(tee)
|
|
// } else {
|