233 lines
4.9 KiB
Go
233 lines
4.9 KiB
Go
package interactive
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/alexballas/go2tv/soapcalls"
|
|
"github.com/gdamore/tcell/v2"
|
|
"github.com/mattn/go-runewidth"
|
|
)
|
|
|
|
// NewScreen .
|
|
type NewScreen struct {
|
|
Current tcell.Screen
|
|
TV *soapcalls.TVPayload
|
|
exitCTXfunc context.CancelFunc
|
|
mediaTitle string
|
|
lastAction string
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
var flipflop bool = true
|
|
|
|
func (p *NewScreen) emitStr(x, y int, style tcell.Style, str string) {
|
|
s := p.Current
|
|
for _, c := range str {
|
|
var comb []rune
|
|
w := runewidth.RuneWidth(c)
|
|
if w == 0 {
|
|
comb = []rune{c}
|
|
c = ' '
|
|
w = 1
|
|
}
|
|
s.SetContent(x, y, c, comb, style)
|
|
x += w
|
|
}
|
|
}
|
|
|
|
// EmitMsg displays the actions to the interactive terminal.
|
|
// Method to implement the screen interface
|
|
func (p *NewScreen) EmitMsg(inputtext string) {
|
|
p.updateLastAction(inputtext)
|
|
s := p.Current
|
|
|
|
p.mu.RLock()
|
|
mediaTitle := p.mediaTitle
|
|
p.mu.RUnlock()
|
|
|
|
titleLen := len("Title: " + mediaTitle)
|
|
w, h := s.Size()
|
|
boldStyle := tcell.StyleDefault.
|
|
Background(tcell.ColorBlack).
|
|
Foreground(tcell.ColorWhite).Bold(true)
|
|
blinkStyle := tcell.StyleDefault.
|
|
Background(tcell.ColorBlack).
|
|
Foreground(tcell.ColorWhite).Blink(true)
|
|
|
|
s.Clear()
|
|
|
|
p.emitStr(w/2-titleLen/2, h/2-2, tcell.StyleDefault, "Title: "+mediaTitle)
|
|
if inputtext == "Waiting for status..." {
|
|
p.emitStr(w/2-len(inputtext)/2, h/2, blinkStyle, inputtext)
|
|
} else {
|
|
p.emitStr(w/2-len(inputtext)/2, h/2, boldStyle, inputtext)
|
|
}
|
|
p.emitStr(1, 1, tcell.StyleDefault, "Press ESC to stop and exit.")
|
|
|
|
isMute := "0"
|
|
var err error
|
|
|
|
if p.TV != nil {
|
|
isMute, err = p.TV.GetMuteSoapCall()
|
|
}
|
|
|
|
if err != nil || isMute == "0" {
|
|
p.emitStr(w/2-len("")/2, h/2+2, tcell.StyleDefault, "")
|
|
} else {
|
|
p.emitStr(w/2-len("MUTED")/2, h/2+2, blinkStyle, "MUTED")
|
|
}
|
|
p.emitStr(w/2-len(`"p" (Play/Pause)`)/2, h/2+4, tcell.StyleDefault, `"p" (Play/Pause)`)
|
|
p.emitStr(w/2-len(`"m" (Mute/Unmute)`)/2, h/2+6, tcell.StyleDefault, `"m" (Mute/Unmute)`)
|
|
p.emitStr(w/2-len(`"Page Up" "Page Down" (Volume Up/Down)`)/2, h/2+8, tcell.StyleDefault, `"Page Up" "Page Down" (Volume Up/Down)`)
|
|
s.Show()
|
|
}
|
|
|
|
// InterInit starts the interactive terminal
|
|
func (p *NewScreen) InterInit(tv *soapcalls.TVPayload, c chan error) {
|
|
p.TV = tv
|
|
|
|
muteChecker := time.NewTicker(1 * time.Second)
|
|
|
|
go func() {
|
|
for range muteChecker.C {
|
|
p.EmitMsg(p.getLastAction())
|
|
}
|
|
}()
|
|
|
|
p.mu.Lock()
|
|
|
|
mediaTitlefromURL, err := url.Parse(tv.MediaURL)
|
|
if err != nil {
|
|
c <- fmt.Errorf("interactive screen error: %w", err)
|
|
return
|
|
}
|
|
p.mediaTitle = strings.TrimLeft(mediaTitlefromURL.Path, "/")
|
|
|
|
p.mu.Unlock()
|
|
|
|
s := p.Current
|
|
if err := s.Init(); err != nil {
|
|
c <- fmt.Errorf("interactive screen error: %w", err)
|
|
return
|
|
}
|
|
|
|
defStyle := tcell.StyleDefault.
|
|
Background(tcell.ColorBlack).
|
|
Foreground(tcell.ColorWhite)
|
|
s.SetStyle(defStyle)
|
|
|
|
p.updateLastAction("Waiting for status...")
|
|
p.EmitMsg(p.getLastAction())
|
|
|
|
// Sending the Play1 action sooner may result
|
|
// in a panic error since we need to properly
|
|
// initialize the tcell window.
|
|
if err := tv.SendtoTV("Play1"); err != nil {
|
|
c <- fmt.Errorf("interactive screen error: %w", err)
|
|
return
|
|
}
|
|
|
|
for {
|
|
switch ev := s.PollEvent().(type) {
|
|
case *tcell.EventResize:
|
|
s.Sync()
|
|
p.EmitMsg(p.getLastAction())
|
|
case *tcell.EventKey:
|
|
p.HandleKeyEvent(ev)
|
|
}
|
|
}
|
|
}
|
|
|
|
// HandleKeyEvent Method to handle all key press events
|
|
func (p *NewScreen) HandleKeyEvent(ev *tcell.EventKey) {
|
|
tv := p.TV
|
|
|
|
if ev.Key() == tcell.KeyEscape {
|
|
_ = tv.SendtoTV("Stop")
|
|
p.Fini()
|
|
}
|
|
|
|
if ev.Key() == tcell.KeyPgUp || ev.Key() == tcell.KeyPgDn {
|
|
currentVolume, err := tv.GetVolumeSoapCall()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
setVolume := currentVolume - 1
|
|
if ev.Key() == tcell.KeyPgUp {
|
|
setVolume = currentVolume + 1
|
|
}
|
|
|
|
stringVolume := strconv.Itoa(setVolume)
|
|
|
|
if err := tv.SetVolumeSoapCall(stringVolume); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
switch ev.Rune() {
|
|
case 'p':
|
|
if flipflop {
|
|
flipflop = false
|
|
_ = tv.SendtoTV("Pause")
|
|
break
|
|
}
|
|
|
|
flipflop = true
|
|
_ = tv.SendtoTV("Play")
|
|
|
|
case 'm':
|
|
currentMute, err := tv.GetMuteSoapCall()
|
|
if err != nil {
|
|
break
|
|
}
|
|
switch currentMute {
|
|
case "1":
|
|
if err = tv.SetMuteSoapCall("0"); err == nil {
|
|
p.EmitMsg(p.getLastAction())
|
|
}
|
|
case "0":
|
|
if err = tv.SetMuteSoapCall("1"); err == nil {
|
|
p.EmitMsg(p.getLastAction())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fini Method to implement the screen interface
|
|
func (p *NewScreen) Fini() {
|
|
p.Current.Fini()
|
|
p.exitCTXfunc()
|
|
}
|
|
|
|
// InitTcellNewScreen .
|
|
func InitTcellNewScreen(ctxCancel context.CancelFunc) (*NewScreen, error) {
|
|
s, err := tcell.NewScreen()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("interactive screen error: %w", err)
|
|
}
|
|
|
|
return &NewScreen{
|
|
Current: s,
|
|
exitCTXfunc: ctxCancel,
|
|
}, nil
|
|
}
|
|
|
|
func (p *NewScreen) getLastAction() string {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
return p.lastAction
|
|
}
|
|
|
|
func (p *NewScreen) updateLastAction(s string) {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
p.lastAction = s
|
|
}
|