Sonically/audio_player.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 {