fcaster/app/model.go
2025-05-29 22:52:44 +02:00

156 lines
3.5 KiB
Go

package main
import (
"errors"
"fmt"
"git.massive.box/massivebox/fcaster/fcast"
"io"
"math"
"net/http"
)
type Model struct {
Controller *Controller
DiscoveredDevices []fcast.DiscoveredHost
Connection *fcast.Connection
EventManager *fcast.EventManager
Time float32
Length float32
Volume float32
}
func (m *Model) DiscoverDevices() ([]string, error) {
devices, err := fcast.Discover()
if err != nil {
return nil, err
}
m.DiscoveredDevices = devices
var names []string
for _, d := range m.DiscoveredDevices {
names = append(names, d.Name)
}
return names, nil
}
func (m *Model) ConnectToDevice(name string) error {
device := &fcast.DiscoveredHost{}
for _, d := range m.DiscoveredDevices {
if d.Name == name {
device = &d
}
}
if device.Name == "" {
return errors.New("device not found")
}
connection, err := fcast.Connect(device.IPv4)
if err != nil {
return err
}
m.Connection = connection
return nil
}
func (m *Model) StartCast(mediaURL string) error {
m.EventManager = &fcast.EventManager{}
m.EventManager.SetHandler(fcast.PlaybackUpdateMessage{}, func(message fcast.Message) {
msg := message.(*fcast.PlaybackUpdateMessage)
m.Length = msg.Duration
m.Time = msg.Time
m.Controller.ReceiverResponse(RespPlaybackUpdate, msg.Duration, msg.Time)
})
m.EventManager.SetHandler(fcast.VolumeUpdateMessage{}, func(message fcast.Message) {
msg := message.(*fcast.VolumeUpdateMessage)
m.Volume = msg.Volume
})
m.EventManager.SetHandler(fcast.PlaybackErrorMessage{}, func(message fcast.Message) {
msg := message.(*fcast.PlaybackErrorMessage)
m.Controller.LogAndShowError(fmt.Errorf("playback error: %s", msg.Message))
})
if mediaURL != "" {
mediaType, err := getMediaType(mediaURL)
if err != nil {
return err
}
err = m.Connection.SendMessage(&fcast.PlayMessage{
Container: mediaType,
Url: mediaURL,
})
if err != nil {
return err
}
}
return m.Connection.ListenForMessages(m.EventManager)
}
func (m *Model) PlayerAction(action int, args ...float32) error {
var message fcast.Message
switch action {
case ActionPlay:
message = &fcast.ResumeMessage{}
break
case ActionPause:
message = &fcast.PauseMessage{}
break
case ActionSkipBack:
message = &fcast.SeekMessage{Time: int(m.Time - 10)}
break
case ActionSkipForward:
message = &fcast.SeekMessage{Time: int(m.Time + 10)}
break
case ActionStop:
message = &fcast.StopMessage{}
break
case ActionVolumeUp:
message = &fcast.SetVolumeMessage{Volume: m.Volume + 0.1}
break
case ActionVolumeDown:
message = &fcast.SetVolumeMessage{Volume: m.Volume - 0.1}
break
case ArgsActionSeek:
if math.Abs(float64(args[0]-m.Time)) < 2 {
return nil
}
message = &fcast.SeekMessage{Time: int(args[0])}
break
}
return m.Connection.SendMessage(message)
}
func getMediaType(url string) (string, error) {
client := &http.Client{}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
req.Header.Set("Range", "bytes=0-512")
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
return "", fmt.Errorf("server returned unexpected status: %s", resp.Status)
}
buffer := make([]byte, 512)
n, err := resp.Body.Read(buffer)
if err != nil && err != io.EOF {
return "", err
}
mimeType := http.DetectContentType(buffer[:n])
return mimeType, nil
}