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 {