Gapless playback improvements. UI improvements
This commit is contained in:
@@ -158,6 +158,37 @@ func playAction(screen *NewScreen) {
|
||||
|
||||
screen.PlayPause.Disable()
|
||||
|
||||
if screen.cancelEnablePlay != nil {
|
||||
screen.cancelEnablePlay()
|
||||
}
|
||||
|
||||
ctx, cancelEnablePlay := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
screen.cancelEnablePlay = cancelEnablePlay
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
|
||||
defer func() { screen.cancelEnablePlay = nil }()
|
||||
|
||||
if errors.Is(ctx.Err(), context.Canceled) {
|
||||
return
|
||||
}
|
||||
|
||||
out, err := screen.tvdata.GetTransportInfo()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch out[0] {
|
||||
case "PLAYING":
|
||||
setPlayPauseView("Pause", screen)
|
||||
screen.updateScreenState("Playing")
|
||||
case "PAUSED":
|
||||
setPlayPauseView("Play", screen)
|
||||
screen.updateScreenState("Paused")
|
||||
}
|
||||
}()
|
||||
|
||||
currentState := screen.getScreenState()
|
||||
|
||||
if currentState == "Paused" {
|
||||
@@ -183,20 +214,20 @@ func playAction(screen *NewScreen) {
|
||||
|
||||
if screen.mediafile == "" && screen.MediaText.Text == "" {
|
||||
check(screen, errors.New("please select a media file or enter a media URL"))
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
|
||||
if screen.controlURL == "" {
|
||||
check(screen, errors.New("please select a device"))
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
|
||||
whereToListen, err := utils.URLtoListenIPandPort(screen.controlURL)
|
||||
check(screen, err)
|
||||
if err != nil {
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -207,14 +238,14 @@ func playAction(screen *NewScreen) {
|
||||
mfile, err := os.Open(screen.mediafile)
|
||||
check(screen, err)
|
||||
if err != nil {
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
|
||||
mediaType, err = utils.GetMimeDetailsFromFile(mfile)
|
||||
check(screen, err)
|
||||
if err != nil {
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -225,7 +256,7 @@ func playAction(screen *NewScreen) {
|
||||
|
||||
callbackPath, err := utils.RandomString()
|
||||
if err != nil {
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -246,21 +277,21 @@ func playAction(screen *NewScreen) {
|
||||
mediaURL, err := utils.StreamURL(context.Background(), screen.MediaText.Text)
|
||||
check(screen, err)
|
||||
if err != nil {
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
|
||||
mediaURLinfo, err := utils.StreamURL(context.Background(), screen.MediaText.Text)
|
||||
check(screen, err)
|
||||
if err != nil {
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
|
||||
mediaType, err = utils.GetMimeDetailsFromStream(mediaURLinfo)
|
||||
check(screen, err)
|
||||
if err != nil {
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -269,7 +300,7 @@ func playAction(screen *NewScreen) {
|
||||
readerToBytes, err := io.ReadAll(mediaURL)
|
||||
mediaURL.Close()
|
||||
if err != nil {
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
mediaFile = readerToBytes
|
||||
@@ -338,6 +369,15 @@ func playAction(screen *NewScreen) {
|
||||
|
||||
}
|
||||
|
||||
func startAfreshPlayButton(screen *NewScreen) {
|
||||
if screen.cancelEnablePlay != nil {
|
||||
screen.cancelEnablePlay()
|
||||
}
|
||||
|
||||
setPlayPauseView("Play", screen)
|
||||
screen.updateScreenState("Stopped")
|
||||
}
|
||||
|
||||
func gaplessMediaWatcher(ctx context.Context, screen *NewScreen, payload *soapcalls.TVPayload) {
|
||||
t := time.NewTicker(1 * time.Second)
|
||||
out:
|
||||
@@ -345,9 +385,14 @@ out:
|
||||
select {
|
||||
case <-t.C:
|
||||
gaplessOption := fyne.CurrentApp().Preferences().StringWithFallback("Gapless", "Disabled")
|
||||
gapless, _ := payload.Gapless()
|
||||
nextURI, _ := payload.Gapless()
|
||||
|
||||
if !gapless && gaplessOption == "Enabled" && screen.NextMediaCheck.Checked {
|
||||
if nextURI == "NOT_IMPLEMENTED" || gaplessOption == "Disabled" {
|
||||
screen.GaplessMediaWatcher = nil
|
||||
break out
|
||||
}
|
||||
|
||||
if nextURI == "" && screen.NextMediaCheck.Checked {
|
||||
screen.MediaText.Text, screen.mediafile = getNextMedia(screen)
|
||||
screen.MediaText.Refresh()
|
||||
|
||||
|
@@ -128,6 +128,37 @@ func playAction(screen *NewScreen) {
|
||||
|
||||
screen.PlayPause.Disable()
|
||||
|
||||
if screen.cancelEnablePlay != nil {
|
||||
screen.cancelEnablePlay()
|
||||
}
|
||||
|
||||
ctx, cancelEnablePlay := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
screen.cancelEnablePlay = cancelEnablePlay
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
|
||||
defer func() { screen.cancelEnablePlay = nil }()
|
||||
|
||||
if errors.Is(ctx.Err(), context.Canceled) {
|
||||
return
|
||||
}
|
||||
|
||||
out, err := screen.tvdata.GetTransportInfo()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch out[0] {
|
||||
case "PLAYING":
|
||||
setPlayPauseView("Pause", screen)
|
||||
screen.updateScreenState("Playing")
|
||||
case "PAUSED":
|
||||
setPlayPauseView("Play", screen)
|
||||
screen.updateScreenState("Paused")
|
||||
}
|
||||
}()
|
||||
|
||||
currentState := screen.getScreenState()
|
||||
|
||||
if currentState == "Paused" {
|
||||
@@ -153,20 +184,20 @@ func playAction(screen *NewScreen) {
|
||||
|
||||
if screen.mediafile == nil && screen.MediaText.Text == "" {
|
||||
check(w, errors.New("please select a media file or enter a media URL"))
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
|
||||
if screen.controlURL == "" {
|
||||
check(w, errors.New("please select a device"))
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
|
||||
whereToListen, err := utils.URLtoListenIPandPort(screen.controlURL)
|
||||
check(w, err)
|
||||
if err != nil {
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -174,7 +205,7 @@ func playAction(screen *NewScreen) {
|
||||
|
||||
callbackPath, err := utils.RandomString()
|
||||
if err != nil {
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -182,21 +213,21 @@ func playAction(screen *NewScreen) {
|
||||
mediaURL, err := storage.OpenFileFromURI(screen.mediafile)
|
||||
check(screen.Current, err)
|
||||
if err != nil {
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
|
||||
mediaURLinfo, err := storage.OpenFileFromURI(screen.mediafile)
|
||||
check(screen.Current, err)
|
||||
if err != nil {
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
|
||||
mediaType, err = utils.GetMimeDetailsFromStream(mediaURLinfo)
|
||||
check(w, err)
|
||||
if err != nil {
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -205,7 +236,7 @@ func playAction(screen *NewScreen) {
|
||||
readerToBytes, err := io.ReadAll(mediaURL)
|
||||
mediaURL.Close()
|
||||
if err != nil {
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
mediaFile = readerToBytes
|
||||
@@ -216,7 +247,7 @@ func playAction(screen *NewScreen) {
|
||||
subsFile, err = storage.OpenFileFromURI(screen.subsfile)
|
||||
check(screen.Current, err)
|
||||
if err != nil {
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -230,21 +261,21 @@ func playAction(screen *NewScreen) {
|
||||
mediaURL, err := utils.StreamURL(context.Background(), screen.MediaText.Text)
|
||||
check(screen.Current, err)
|
||||
if err != nil {
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
|
||||
mediaURLinfo, err := utils.StreamURL(context.Background(), screen.MediaText.Text)
|
||||
check(screen.Current, err)
|
||||
if err != nil {
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
|
||||
mediaType, err = utils.GetMimeDetailsFromStream(mediaURLinfo)
|
||||
check(w, err)
|
||||
if err != nil {
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -253,7 +284,7 @@ func playAction(screen *NewScreen) {
|
||||
readerToBytes, err := io.ReadAll(mediaURL)
|
||||
mediaURL.Close()
|
||||
if err != nil {
|
||||
screen.PlayPause.Enable()
|
||||
startAfreshPlayButton(screen)
|
||||
return
|
||||
}
|
||||
mediaFile = readerToBytes
|
||||
@@ -394,3 +425,12 @@ func volumeAction(screen *NewScreen, up bool) {
|
||||
check(w, errors.New("could not send volume action"))
|
||||
}
|
||||
}
|
||||
|
||||
func startAfreshPlayButton(screen *NewScreen) {
|
||||
if screen.cancelEnablePlay != nil {
|
||||
screen.cancelEnablePlay()
|
||||
}
|
||||
|
||||
setPlayPauseView("Play", screen)
|
||||
screen.updateScreenState("Stopped")
|
||||
}
|
||||
|
@@ -24,12 +24,12 @@ import (
|
||||
|
||||
// NewScreen .
|
||||
type NewScreen struct {
|
||||
muError sync.RWMutex
|
||||
mu sync.RWMutex
|
||||
serverStopCTX context.Context
|
||||
muError sync.RWMutex
|
||||
Current fyne.Window
|
||||
VolumeUp *widget.Button
|
||||
tvdata *soapcalls.TVPayload
|
||||
VolumeDown *widget.Button
|
||||
MediaText *widget.Entry
|
||||
Debug *debugWriter
|
||||
tabs *container.AppTabs
|
||||
CheckVersion *widget.Button
|
||||
SubsText *widget.Entry
|
||||
@@ -38,26 +38,27 @@ type NewScreen struct {
|
||||
Stop *widget.Button
|
||||
DeviceList *widget.List
|
||||
httpserver *httphandlers.HTTPserver
|
||||
MediaText *widget.Entry
|
||||
serverStopCTX context.Context
|
||||
ExternalMediaURL *widget.Check
|
||||
GaplessMediaWatcher func(context.Context, *NewScreen, *soapcalls.TVPayload)
|
||||
cancelEnablePlay context.CancelFunc
|
||||
MuteUnmute *widget.Button
|
||||
VolumeDown *widget.Button
|
||||
Debug *debugWriter
|
||||
NextMediaCheck *widget.Check
|
||||
VolumeUp *widget.Button
|
||||
tvdata *soapcalls.TVPayload
|
||||
selectedDevice devType
|
||||
State string
|
||||
subsfile string
|
||||
currentmfolder string
|
||||
mediafile string
|
||||
eventlURL string
|
||||
controlURL string
|
||||
renderingControlURL string
|
||||
connectionManagerURL string
|
||||
currentmfolder string
|
||||
State string
|
||||
version string
|
||||
eventlURL string
|
||||
mediafile string
|
||||
subsfile string
|
||||
mediaFormats []string
|
||||
Transcode bool
|
||||
ErrorVisible bool
|
||||
NextMediaCheck *widget.Check
|
||||
Medialoop bool
|
||||
Hotkeys bool
|
||||
}
|
||||
@@ -289,6 +290,10 @@ func getNextPossibleSubs(v string, screen *NewScreen) (string, string) {
|
||||
}
|
||||
|
||||
func setPlayPauseView(s string, screen *NewScreen) {
|
||||
if screen.cancelEnablePlay != nil {
|
||||
screen.cancelEnablePlay()
|
||||
}
|
||||
|
||||
screen.PlayPause.Enable()
|
||||
switch s {
|
||||
case "Play":
|
||||
|
@@ -32,6 +32,7 @@ type NewScreen struct {
|
||||
CheckVersion *widget.Button
|
||||
CustomSubsCheck *widget.Check
|
||||
ExternalMediaURL *widget.Check
|
||||
cancelEnablePlay context.CancelFunc
|
||||
MediaText *widget.Entry
|
||||
SubsText *widget.Entry
|
||||
DeviceList *widget.List
|
||||
@@ -157,6 +158,10 @@ func (p *NewScreen) getScreenState() string {
|
||||
}
|
||||
|
||||
func setPlayPauseView(s string, screen *NewScreen) {
|
||||
if screen.cancelEnablePlay != nil {
|
||||
screen.cancelEnablePlay()
|
||||
}
|
||||
|
||||
screen.PlayPause.Enable()
|
||||
switch s {
|
||||
case "Play":
|
||||
|
@@ -1,3 +1,6 @@
|
||||
//go:build !(android || ios)
|
||||
// +build !android,!ios
|
||||
|
||||
package gui
|
||||
|
||||
import (
|
||||
@@ -109,8 +112,26 @@ func settingsWindow(s *NewScreen) fyne.CanvasObject {
|
||||
gaplessdropdown := widget.NewSelect([]string{"Enabled", "Disabled"}, func(ss string) {
|
||||
fyne.CurrentApp().Preferences().SetString("Gapless", ss)
|
||||
if s.NextMediaCheck.Checked {
|
||||
s.NextMediaCheck.SetChecked(false)
|
||||
s.NextMediaCheck.SetChecked(true)
|
||||
switch ss {
|
||||
case "Enabled":
|
||||
switch s.State {
|
||||
case "Playing", "Paused":
|
||||
newTVPayload, err := queueNext(s, false)
|
||||
if err == nil && s.GaplessMediaWatcher == nil {
|
||||
s.GaplessMediaWatcher = gaplessMediaWatcher
|
||||
go s.GaplessMediaWatcher(s.serverStopCTX, s, newTVPayload)
|
||||
}
|
||||
}
|
||||
case "Disabled":
|
||||
// We're disabling gapless playback. If for some reason
|
||||
// we fail to clear the NextURI it would be best to stop and
|
||||
// avoid inconsistent where gapless playback appears disabled
|
||||
// but in reality it's not.
|
||||
_, err := queueNext(s, true)
|
||||
if err != nil {
|
||||
stopAction(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
gaplessOption := fyne.CurrentApp().Preferences().StringWithFallback("Gapless", "Disabled")
|
||||
|
@@ -276,6 +276,24 @@ type getMediaInfoAction struct {
|
||||
InstanceID string
|
||||
}
|
||||
|
||||
type getTransportInfoEnvelope struct {
|
||||
XMLName xml.Name `xml:"s:Envelope"`
|
||||
Schema string `xml:"xmlns:s,attr"`
|
||||
Encoding string `xml:"s:encodingStyle,attr"`
|
||||
GetTransportInfoBody getTransportInfoBody `xml:"s:Body"`
|
||||
}
|
||||
|
||||
type getTransportInfoBody struct {
|
||||
XMLName xml.Name `xml:"s:Body"`
|
||||
GetTransportInfoAction getTransportInfoAction `xml:"u:GetTransportInfo"`
|
||||
}
|
||||
|
||||
type getTransportInfoAction struct {
|
||||
XMLName xml.Name `xml:"u:GetTransportInfo"`
|
||||
AVTransport string `xml:"xmlns:u,attr"`
|
||||
InstanceID string
|
||||
}
|
||||
|
||||
func setAVTransportSoapBuild(tvdata *TVPayload) ([]byte, error) {
|
||||
mediaTypeSlice := strings.Split(tvdata.MediaType, "/")
|
||||
seekflag := "00"
|
||||
@@ -775,3 +793,26 @@ func getMediaInfoSoapBuild() ([]byte, error) {
|
||||
|
||||
return append(xmlStart, b...), nil
|
||||
}
|
||||
|
||||
func getTransportInfoSoapBuild() ([]byte, error) {
|
||||
d := getTransportInfoEnvelope{
|
||||
XMLName: xml.Name{},
|
||||
Schema: "http://schemas.xmlsoap.org/soap/envelope/",
|
||||
Encoding: "http://schemas.xmlsoap.org/soap/encoding/",
|
||||
GetTransportInfoBody: getTransportInfoBody{
|
||||
XMLName: xml.Name{},
|
||||
GetTransportInfoAction: getTransportInfoAction{
|
||||
XMLName: xml.Name{},
|
||||
AVTransport: "urn:schemas-upnp-org:service:AVTransport:1",
|
||||
InstanceID: "0",
|
||||
},
|
||||
},
|
||||
}
|
||||
xmlStart := []byte(`<?xml version="1.0" encoding="utf-8"?>`)
|
||||
b, err := xml.Marshal(d)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getTransportInfoSoapBuild Marshal error: %w", err)
|
||||
}
|
||||
|
||||
return append(xmlStart, b...), nil
|
||||
}
|
||||
|
@@ -266,3 +266,27 @@ func TestSetVolumeSoapBuild(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTransportInfoSoapBuild(t *testing.T) {
|
||||
tt := []struct {
|
||||
name string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
`getTransportInfoSoapBuildTest #1`,
|
||||
`<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:GetTransportInfo xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"><InstanceID>0</InstanceID></u:GetTransportInfo></s:Body></s:Envelope>`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
out, err := getTransportInfoSoapBuild()
|
||||
if err != nil {
|
||||
t.Fatalf("%s: Failed to call getTransportInfoSoapBuild due to %s", tc.name, err.Error())
|
||||
}
|
||||
if string(out) != tc.want {
|
||||
t.Fatalf("%s: got: %s, want: %s.", tc.name, out, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -122,6 +122,23 @@ type getMediaInfoResponse struct {
|
||||
} `xml:"Body"`
|
||||
}
|
||||
|
||||
type getTransportInfoResponse struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Text string `xml:",chardata"`
|
||||
S string `xml:"s,attr"`
|
||||
EncodingStyle string `xml:"encodingStyle,attr"`
|
||||
Body struct {
|
||||
Text string `xml:",chardata"`
|
||||
GetTransportInfoResponse struct {
|
||||
Text string `xml:",chardata"`
|
||||
U string `xml:"u,attr"`
|
||||
CurrentTransportState string `xml:"CurrentTransportState"`
|
||||
CurrentTransportStatus string `xml:"CurrentTransportStatus"`
|
||||
CurrentSpeed string `xml:"CurrentSpeed"`
|
||||
} `xml:"GetTransportInfoResponse"`
|
||||
} `xml:"Body"`
|
||||
}
|
||||
|
||||
func (p *TVPayload) setAVTransportSoapCall() error {
|
||||
parsedURLtransport, err := url.Parse(p.ControlURL)
|
||||
if err != nil {
|
||||
@@ -890,16 +907,16 @@ func (p *TVPayload) GetProtocolInfo() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Gapless requests our device's media info and checks if Next URI exists.
|
||||
func (p *TVPayload) Gapless() (bool, error) {
|
||||
// Gapless requests our device's media info and returns the Next URI.
|
||||
func (p *TVPayload) Gapless() (string, error) {
|
||||
if p == nil {
|
||||
return false, errors.New("Gapless, nil tvdata")
|
||||
return "", errors.New("Gapless, nil tvdata")
|
||||
}
|
||||
|
||||
parsedURLtransport, err := url.Parse(p.ControlURL)
|
||||
if err != nil {
|
||||
p.Log().Error().Str("Method", "Gapless").Str("Action", "URL Parse").Err(err).Msg("")
|
||||
return false, fmt.Errorf("Gapless parse error: %w", err)
|
||||
return "", fmt.Errorf("Gapless parse error: %w", err)
|
||||
}
|
||||
|
||||
var xmlbuilder []byte
|
||||
@@ -907,14 +924,14 @@ func (p *TVPayload) Gapless() (bool, error) {
|
||||
xmlbuilder, err = getMediaInfoSoapBuild()
|
||||
if err != nil {
|
||||
p.Log().Error().Str("Method", "Gapless").Str("Action", "Build").Err(err).Msg("")
|
||||
return false, fmt.Errorf("Gapless build error: %w", err)
|
||||
return "", fmt.Errorf("Gapless build error: %w", err)
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("POST", parsedURLtransport.String(), bytes.NewReader(xmlbuilder))
|
||||
if err != nil {
|
||||
p.Log().Error().Str("Method", "Gapless").Str("Action", "Prepare POST").Err(err).Msg("")
|
||||
return false, fmt.Errorf("Gapless POST error: %w", err)
|
||||
return "", fmt.Errorf("Gapless POST error: %w", err)
|
||||
}
|
||||
req.Header = http.Header{
|
||||
"SOAPAction": []string{`"urn:schemas-upnp-org:service:AVTransport:1#GetMediaInfo"`},
|
||||
@@ -926,7 +943,7 @@ func (p *TVPayload) Gapless() (bool, error) {
|
||||
headerBytesReq, err := json.Marshal(req.Header)
|
||||
if err != nil {
|
||||
p.Log().Error().Str("Method", "Gapless").Str("Action", "Header Marshaling").Err(err).Msg("")
|
||||
return false, fmt.Errorf("Gapless Request Marshaling error: %w", err)
|
||||
return "", fmt.Errorf("Gapless Request Marshaling error: %w", err)
|
||||
}
|
||||
|
||||
p.Log().Debug().
|
||||
@@ -936,20 +953,20 @@ func (p *TVPayload) Gapless() (bool, error) {
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Gapless Do POST error: %w", err)
|
||||
return "", fmt.Errorf("Gapless Do POST error: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
headerBytesRes, err := json.Marshal(res.Header)
|
||||
if err != nil {
|
||||
p.Log().Error().Str("Method", "Gapless").Str("Action", "Header Marshaling #2").Err(err).Msg("")
|
||||
return false, fmt.Errorf("Gapless Response Marshaling error: %w", err)
|
||||
return "", fmt.Errorf("Gapless Response Marshaling error: %w", err)
|
||||
}
|
||||
|
||||
resBytes, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
p.Log().Error().Str("Method", "Gapless").Str("Action", "Readall").Err(err).Msg("")
|
||||
return false, fmt.Errorf("Gapless Failed to read response: %w", err)
|
||||
return "", fmt.Errorf("Gapless Failed to read response: %w", err)
|
||||
}
|
||||
|
||||
p.Log().Debug().
|
||||
@@ -961,15 +978,94 @@ func (p *TVPayload) Gapless() (bool, error) {
|
||||
|
||||
if err := xml.Unmarshal(resBytes, &respMedialInfo); err != nil {
|
||||
p.Log().Error().Str("Method", "Gapless").Str("Action", "Unmarshal").Err(err).Msg("")
|
||||
return false, fmt.Errorf("Gapless Failed to unmarshal response: %w", err)
|
||||
return "", fmt.Errorf("Gapless Failed to unmarshal response: %w", err)
|
||||
}
|
||||
|
||||
nextURI := respMedialInfo.Body.GetMediaInfoResponse.NextURI
|
||||
if nextURI != "NOT_IMPLEMENTED" && nextURI != "" {
|
||||
return true, nil
|
||||
|
||||
return nextURI, nil
|
||||
}
|
||||
|
||||
// GetTransportInfo .
|
||||
func (p *TVPayload) GetTransportInfo() ([]string, error) {
|
||||
if p == nil {
|
||||
return nil, errors.New("GetTransportInfo, nil tvdata")
|
||||
}
|
||||
|
||||
return false, nil
|
||||
parsedURLtransport, err := url.Parse(p.ControlURL)
|
||||
if err != nil {
|
||||
p.Log().Error().Str("Method", "GetTransportInfo").Str("Action", "URL Parse").Err(err).Msg("")
|
||||
return nil, fmt.Errorf("GetTransportInfo parse error: %w", err)
|
||||
}
|
||||
|
||||
var xmlbuilder []byte
|
||||
|
||||
xmlbuilder, err = getTransportInfoSoapBuild()
|
||||
if err != nil {
|
||||
p.Log().Error().Str("Method", "GetTransportInfo").Str("Action", "Build").Err(err).Msg("")
|
||||
return nil, fmt.Errorf("GetTransportInfo build error: %w", err)
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("POST", parsedURLtransport.String(), bytes.NewReader(xmlbuilder))
|
||||
if err != nil {
|
||||
p.Log().Error().Str("Method", "GetTransportInfo").Str("Action", "Prepare POST").Err(err).Msg("")
|
||||
return nil, fmt.Errorf("GetTransportInfo POST error: %w", err)
|
||||
}
|
||||
req.Header = http.Header{
|
||||
"SOAPAction": []string{`"urn:schemas-upnp-org:service:AVTransport:1#GetTransportInfo"`},
|
||||
"content-type": []string{"text/xml"},
|
||||
"charset": []string{"utf-8"},
|
||||
"Connection": []string{"close"},
|
||||
}
|
||||
|
||||
headerBytesReq, err := json.Marshal(req.Header)
|
||||
if err != nil {
|
||||
p.Log().Error().Str("Method", "GetTransportInfo").Str("Action", "Header Marshaling").Err(err).Msg("")
|
||||
return nil, fmt.Errorf("GetTransportInfo Request Marshaling error: %w", err)
|
||||
}
|
||||
|
||||
p.Log().Debug().
|
||||
Str("Method", "GetTransportInfo").Str("Action", "Request").
|
||||
RawJSON("Headers", headerBytesReq).
|
||||
Msg(string(xmlbuilder))
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetTransportInfo Do POST error: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
headerBytesRes, err := json.Marshal(res.Header)
|
||||
if err != nil {
|
||||
p.Log().Error().Str("Method", "GetTransportInfo").Str("Action", "Header Marshaling #2").Err(err).Msg("")
|
||||
return nil, fmt.Errorf("GetTransportInfo Response Marshaling error: %w", err)
|
||||
}
|
||||
|
||||
resBytes, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
p.Log().Error().Str("Method", "GetTransportInfo").Str("Action", "Readall").Err(err).Msg("")
|
||||
return nil, fmt.Errorf("GetTransportInfo Failed to read response: %w", err)
|
||||
}
|
||||
|
||||
p.Log().Debug().
|
||||
Str("Method", "GetTransportInfo").Str("Action", "Response").Str("Status Code", strconv.Itoa(res.StatusCode)).
|
||||
RawJSON("Headers", headerBytesRes).
|
||||
Msg(string(resBytes))
|
||||
|
||||
var respTransportInfo getTransportInfoResponse
|
||||
|
||||
if err := xml.Unmarshal(resBytes, &respTransportInfo); err != nil {
|
||||
p.Log().Error().Str("Method", "GetTransportInfo").Str("Action", "Unmarshal").Err(err).Msg("")
|
||||
return nil, fmt.Errorf("GetTransportInfo Failed to unmarshal response: %w", err)
|
||||
}
|
||||
|
||||
r := respTransportInfo.Body.GetTransportInfoResponse
|
||||
state := r.CurrentTransportState
|
||||
status := r.CurrentTransportStatus
|
||||
speed := r.CurrentSpeed
|
||||
|
||||
return []string{state, status, speed}, nil
|
||||
}
|
||||
|
||||
// SendtoTV is a higher level method that gracefully handles the various
|
||||
|
Reference in New Issue
Block a user