Files
go2tv/internal/interactive/interactive.go
2024-01-02 14:03:22 +02:00

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
}